JavaWeb

本文最后更新于:2024年6月21日 凌晨

一、JDBC 原理及访问数据库

1. JDBC

JDBC:Java DataBase Connectivity 可以为多种关系型数据库 DBMS 提供统一的访问方式,用 Java 操作数据库

JDBC 实现原理:Java 通过 JDBC 操作 JDBC DriverManager 管理不同数据驱动。通过 JDBC 的API(包括接口、方法、类)

2.JDBC API 主要功能:

三件事,具体是通过以下类/接口实现:

DriverManager:管理 jdbc 驱动

Connection:连接(通过 DriverManager 产生)

Statement (PreparedStatement):增删改查 (通过 Connection 产生)

CallableStatement:调用数据库中的 存储过程/ 存储函数 (通过 Connection 产生)

Statement、PreparedStatement、CallableStatement 都是由 Connection 产生的

Result:返回的结果集 (上面的 Statement 等产生)

ResultSet:保存结果集 select * from xxx

next ():光标下移,判断是否有下一条数据;true/false

previous ():true/false

getXxx (字段值|位置):获取具体的字段值

2.1 Connection 产生操作数据库的对象:

Connection 产生 Statement 对象:createStatement ()

Connection 产生 PreparedStatement 对象:prepareStatement ()

Connection 产生 CallableStatement 对象:prepareCall ();

2.2 Statement 操作数据库:

增删改:executeUpdate()

查询:executeQuery();

2.3 PreparedStatement 操作数据库:

public interface PreparedStatement extends Statement

因此

增删改:executeUpdate()

查询:executeQuery();

赋值操作 setXxx();

2.4 PreparedStatement 与 Statement 在使用时的区别:

  1. Statement:

    sql

    executeUpdate(sql)

  2. PreparedStatement:

    sql(可能存在占位符 ? )

    在创建 PreparedStatement 对象时,将 sql 预编译 prepareStatement(sql)

    executeUpdate()

    setXxx()替换占位符

推荐使用 prepareStatement :原因如下:

  1. 编码更加简便(避免了字符串的拼接)

  2. 提高性能(因为有预编译操作,预编译只执行一次)

  3. 安全(可以有效防止 sql 注入)

    sql 注入:将客户端输入的内容 和 开发人员的 SQL 语句 混为一体

2.5 CallableStatement:调用 存储过程、存储函数

connection.prepareCall(存储过程或存储函数名)

参数格式:

存储过程(无返回值 return,用 Out 参数代替):

{call 存储过程名(参数列表)}

存储函数(有返回值):

{ ? = call 存储函数名(参数列表)}

2.5.1 存储过程

构造数据库存储过程

create or replace procedure addTwoNum ( num1 in number, num2 in number, result out number)

as

begin

​ result:=num1+num2;

end;

/

cstmt = connection.prepareCall("call addTwoNum(?,?,?)");
cstmt.setInt(1, 10);
cstmt.setInt(2, 10);
cstmt.execute(); // num1 + num2,execute()之前处理输入参数,之后处理输出参数
// 设置输出参数的类型
cstmt.registerOutParameter(3, Types.INTEGER);
int result = cstmt.getInt(3);// 获取计算结果
System.out.println(result);

JDBC 调用存储过程的步骤:

a. 产生 调用存储过程的对象(CallableStatement) cstmt = connection.prepareCall(“call addTwoNum(?,?,?)”);

b. 通过 setXxx()处理 输出参数值 cstmt.setInt(1,..);

c. 通过 registerOutParameter();处理输出参数类型

d. cstmt.execute()执行

e. 接收输出值(返回值)getXxx()

2.5.2 存储函数

调用存储函数

create or replace function addTwoNumfunction ( num1 in number, num2 in number, result reutnr number)

return number

as

result number;

begin

result:=num1+num2;

return result;

end;

/

JDBC 调用存储函数与调用存储过程的区别:

在调用时,注意参数 “? = call addTwoNumfunction(?,?)”

3. JDBC 访问数据库的具体步骤:

a. 导入驱动,加载具体的驱动类

b. 与数据库建立连接

c. 发送 sql,执行

d. 处理结果集(查询)

4. 数据库驱动

数据库驱动 驱动 jar 具体驱动类 连接字符串
MySQL mysql-connector-java-x.jar com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/数据库实例名

使用 jdbc 操作数据库时,如果对数据库进行了更换,只需要替换:驱动、具体驱动类、连续字符串、用户名、密码

5. 用 java 实现对数据库的增删改、查

public class JDBCDemo {
    private static final String URL = "jdbc:mysql://localhost:3306/bookdb";
    private static final String USERNAME = "root";
    private static final String PWD = "159357";
    public static void update() { // 增删改
        Connection connection = null;
        Statement stmt = null;
        try {
            //        a.  导入驱动,加载具体的驱动类
            Class.forName("com.mysql.cj.jdbc.Driver"); // 加载具体的驱动类
//        b. 与数据库建立连接
            connection = DriverManager.getConnection(URL, USERNAME, PWD);
//        c. 发送sql,执行(增删改、查)
            stmt = connection.createStatement();
//            String sql = "insert into user values(4,'df','26262')";
//            String sql = "update user set username='low' where id=3";
            String sql = "delete from user where id=4";
            // 执行SQL语句
            int count = stmt.executeUpdate(sql);// 返回值表示 增删改 几条数据
            // d.处理结果
            if (count > 0) {
                System.out.println("操作成功!");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
            if (stmt != null) stmt.close();
                if (connection != null) connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void query() { // 查
        Connection connection = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //        a.  导入驱动,加载具体的驱动类
            Class.forName("com.mysql.cj.jdbc.Driver"); // 加载具体的驱动类
//        b. 与数据库建立连接
            connection = DriverManager.getConnection(URL, USERNAME, PWD);
//        c. 发送sql,执行(查)
            stmt = connection.createStatement();
            String sql = "select * from user";
            // 执行SQL语句(增删改executeUpdate(),查executeQuery())
             rs = stmt.executeQuery(sql);
            // d.处理结果
            while (rs.next()) {
                int id = rs.getInt("id");
                String username = rs.getString("username");
                String pwd = rs.getString("pwd");

//                int id = rs.getInt(1);    下标:从1开始计数
//                String username = rs.getString(2);
//                String pwd = rs.getString(3);
                System.out.println(id+"--"+username+"--"+pwd+"--");

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (rs != null) rs.close();
                if (stmt != null) stmt.close();
                if (connection != null) connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
//        update();
        query();
    }
}

6. 总结 JDBC(模板、八股文)

try{

a. 导入驱动包、加载具体驱动类 Class.forName(“具体驱动类”)

b. 与数据库建立连接 connection = DriverManager.getConnection(…);

c. 通过 connection,获取操作数据库的对象(Statement\preparedStatement\callablestatement)

d.(查询)处理结果集 rs = pstmt.executeQuery()

while(rs.next()) { rs.getXxx(…) };

} catch (ClassNotFoundException e) {

} catch(SQLException e){

} catch(Exception e){

} finally {

// 打开顺序与关闭顺序相反

if (rs!= null) rs.close();

if (stmt != null) stmt.close();

if (connection!= null) connection.close();

}

–jdbc 中,除了 Class.forName()抛出 ClassNotFoundException,其余方法全部抛 SQLException

7. 处理 CLUB[Text]/BLOB 类型

处理稍大型数据:

a. 存储路径 E:\

通过 JDBC 存储文件路径,然后根据 IO 操作处理

b.

CLOB:大文本类型 (小说-> 数据)

BLOB:二进制

clob:

  1. 先通过 pstmt 的 ? 代替小说内容(占位符)
  2. 再通过 pstmt.setCharacterStream(2, reader , (int) file.length() );将上一步的 ? 替换为小说流,注意第三个参数需要是 Int 类型

  1. 通过 Reader reader = rs.getCharacterStream(“NOVEL”); 将 cloc 类型的数据保存到 Reader 对象中
  2. 将 Reader 通过 Writer 输出即可。

blob 与 clob 操作类似

把 CharacterStream 变为 BinaryStream

二、JSP 访问数据库

JSP 就是在 html 中嵌套 java 代码,因此 java 代码可以写在 jsp 中(<%……%>)

导包操作:java 项目:1. Jar 复制到工程中 2. 右键该 Jar :build path

Web 项目:jar 复制到 WEB-INF 的 lib 里面

核心:将 java 代码中的 JDBC 代码,复制到 JSP 中的 <% … %>

注意:如果 jsp 出现错误:The import Xxx cannot be resolved…

尝试解决步骤:

a. (可能是 JDK、tomcat 版本不一样)

b. 清空各种缓存

c. 删除之前的 tomcat,重新配置 tomcat、重启电脑等

d. 如果类之前没有包,则将该类加入包中

1. JavaBean

我们将 jsp 中登录操作的代码 转移到了 LoginDao.java;其中 LoginDao 类 就称之为 JavaBean。

JavaBean 的作用:

a. 减轻了 jsp 的复杂度

b. 提高了代码的复用率(以后任何地方的 登录操作,都可以通过调用 LoginDao 实现)

JavaBean(就是一个类)的定义:满足以下两点,就可以称为 JavaBean

a. public 修饰的类,public 无参构造

b. 所有属性(如果有)都是 private,提供 set/get (如果 boolean 则 get 可以替换 is)

使用层面,Java 分为 2 大类:

a. 封装业务逻辑的 JavaBean (Login.java 封装了登录逻辑) —逻辑

可以将 jsp 中的 JDBC 代码,封装到 Login.java 中(Login.java)

b. 封装数据的 JavaBean (实体类,Student.java Person.java) —数据

对应于数据库中的一张表

Login login = new Login(username , upwd); 即用 Login 对象 封装了 2 个数据(用户名 和 密码)

封装数据的 JavaBean 对应于数据库一张表(Login(name, pwd))

封装业务逻辑的 JavaBean 用于操作 一个封装数据的 JavaBean

可以发现使用,JavaBean 可以简化 代码 (jsp->jsp+java)、提供代码复用(LoginDao.java)

三、MVC 设计模式:

M:Model 模型:一个功能。用 JavaBean 实现。

V:View 视图:用于请求,以及与用户交互。使用 html js css jsp jquery 等前端

C:Controller 控制器:接受请求,将请求跳转到模型进行处理;模型处理完毕后,再将处理的结果返回给 请求处。可以用 jsp 实现,但一般建议使用 Servlet 实现控制器。

JSP->Java(Servlet)->JSP

四、Servlet

Java 类必须符合一定的 规范:
a. 必须继承 javax.servlet.http.HttpServlet

b. 重写其中的 doGet()或 doPost()方法

doGet(): 接受 并处 所有 get 提交方式的请求

doPost(): 接受 并处 所有 post 提交方式的请求

Servlet 要想使用,必须配置

Servlet2.5: web.xml

Servlet3.0: @WebServlet

Servlet2.5: web.xml

项目的根目录:web、src

<a href="WelcomeServlet">WelcomeServlet</a> 所在的jsp是在web目录中

因此 发出的请求 WelcomeServlet 是去请求项目的根目录。

Servlet 流程:

请求 -> -> 根据 中的 去匹配 中的 ,然后寻找到 ,最终将请求交由该 执行

<servlet>
    <servlet-name>WelcomeServlet</servlet-name>
    <servlet-class>servlet.WelcomeServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>WelcomeServlet</servlet-name>
    <url-pattern>/WelcomeServlet</url-pattern>
</servlet-mapping>

Servlet3.0 与 Servlet2.5 的区别:

Servlet3.0 不需要在 web.xml 中配置,但需要在 Servlet 类的定义处之上编写

注释 @WebServlet(“url-pattern 的值”)

匹配流程:请求地址 与 @WebServlet 中的值 进行匹配,如果匹配成功,则说明请求的就是该注解所对应的类

根路径:/:

项目根目录:web src(所有的构建路径)

例如:在 web 中有一个文件 index.jsp

src 中有一个 Servlet.java

如果:index.jsp 中请求 <a href=”abc” >…,则 寻找范围:既会在 src 根目录中寻找 也会在 web 中寻找

如果:index.jsp 中请求 <a href=”a/abc” >…,寻找范围:现在 src 或 web 中找 a 目录,然后再在 a 目录中找 abc

/:

web.xml 中的 / :代表的是项目路径 http://localhost:8888/Servlet4Project_war_exploded/

jsp 中的 / :代表服务器根路径 http://localhost:8888/

构建路径、web:根目录

Servlet 生命周期:5 个阶段

加载

初始化:init(),该方法会在 Servlet 被加载并实例化的以后 执行

服务:service() ->doGet() doPost

销毁:destroy(),Servlet 被系统回收时执行

卸载

init():

a. 默认第一次访问 Servlet 时会被执行 (只执行一次)

b. 可以修改为 Tomcat 启动时自动执行

Ⅰ.Servlet2.5: web.xml 中

<servlet>
    <servlet-name>a</servlet-name>
    <servlet-class>servlet.WelcomeServlet</servlet-class>
    <load-on-startup>1</load-on-startup>  其中'1'代表第一次
</servlet>

Ⅱ.Servlet3.0

@WebServlet(value=”/WebcomeServlet” , loadOnStartup=1 )

service() -> doGet() doPost():调用几次,则执行几次

destroy():关闭 tomcat 服务时,执行一次。

Servlet API:

由两个软件包组成:对应于 HPPT 协议的软件包、对应于除了 HPPT 协议以外的其他软件包即 Servlet API 可以适用于 任何 通信协议

我们学习的 Servlet,是位于 javax.servlet.http 包中的类和接口,是基础 HTTP 协议。

Servlet 继承关系:

ServletConfig:接口

getServletContext():获取 Servlet 上下文对象 applicatio

String getInitParameter(String name):在当前 Servlet 范围内,获取名为 name 的参数值(初始化参数)

a. ServletContext 中的常见方法(application):

getContextPath():相对路径

getRealPath():绝对路径

setAttribute()、getAttribute()

—>

String getInitParameter(String name):在当前 web 范围内,获取名为 name 的参数值(初始化参数)

HttpServletRequest 中的方法:(同 request),例如 setAttrite()、getCookies()、getMethod()

HttpServletResponse 中的方法:同 response

使用建议:

Servlet:一个 Servlet 对应一个功能,因此 如果有增删改查 4 个功能,则需要创建 4 个 Servlet

五、三层架构:

与 MVC 设计模式的目标一致:都是为了 解耦合、提高代码复用;

区别:二者对项目理解的角度不同。

三层组成:

表示层(USL,User Show Layer;视图层)

-前台:对应于 MVC 中的 View:用于和用户交互、界面的显示

jsp js html css jquery 等 web 前端技术

代码位置:web

-后台:对应于 MVC 中 Controller,用于 控制跳转、调用业务逻辑层

Servlet(SpringMVC Struts2)

代码位置:一般位于 xxx.servlet 包中

业务逻辑层(BLL,Business Logic Layer;Service 层)

-接受表示层的请求,调用

-组装数据访问层,逻辑性的操作(增删改查,删:查+删)

一般位于 xxx.service 包(也可以成为: xxx.manager,xxx.bll)

数据访问层(DAL,Data Access Layer;Dao 层)

-直接访问数据库的操作,原子性的操作(增删改查)

一般位于 xxx.dao 包

三层间的关系:

上层 将请求传递给下层,下层处理后返回给上层

上层依赖于下层,依赖:代码的理解,就是持有成员变量

数据库是由数据访问层去访问的。

六、MVC 与三层架构的区别

image-20220405173616501

七、Servlet 中使用 jsp 功能

out

out 对象在 servlet 中使用

PrintWriter out = response.getWriter();

session

request.getSession();

application

request.getServletContext();

八、分页 SQL

1、分页

要实现分页,必须要知道 某一页的 数据 从哪里开始 到哪里结束

页面大小:每页显示的数据量

假设每页显示 10 条数据

sqlserver/oracle:从 1 开始计数

第 n 页 开始 结束

1 1 10

2 11 20

3 21 30

n (n-1)×10+1 n×10

mysql:从 0 开始计数

0 0 9

1 10 19

2 20 29

n n×10 (n+1)×10-1

结论:

分页: 第 n 页的数据: 第(n-1)×10+1 条 – 第 n×10 条

MySQL 实现分页的 sql:

limit 从第几页开始,每页显示多少条

第 0 页

select * from student limit 0, 10;

第 1 页

select * from student limit 10, 10;

第 2 页

select * from student limit 20, 10;

第 n 页

select * from student limit n×10, 10;

MySQL 的分页语句

select * from Student limit 页数 × 页面大小,页面大小;

oracle 分页:

sqlserver/oracle:从 1 开始计数

第 n 页 开始 结束

1 1 10

2 11 20

3 21 30

n (n-1)×10+1 n×10

select * from student where sno >=(n-1)×10+1 and sno <= n×10; –此种写法的前提:必须是 Id 值连续,否则无法显示

oracle 分页查询语句:

select * from

(

select rownum r, t._ from (select s._ from student s order by sno asc) t

)

where r >(n-1)×10+1 and r <= n×10;

优化:

select * from

(

select rownum r, t._ from (select s._ from student s order by sno asc) t

where rownum <= n×10

)

where r >(n-1)×10+1 and r <= n×10;

SQLServer 分页:

row_number() over(字段)

select * from

(

select row_number() over (sno order by sno asc) as r, * from student

where r <= n×10

)

where r >(n-1)×10+1 and r <= n×10;

SQLServer 分页 sql 与 oralce 分页 sql 的区别:1.rownum, row_number() 2.oracle 需要排序

分页实现:

5 个变量(属性)

1.数据总数 100 103 (查数据库,select count(*)…)

2.页面大小(每页显示的数据条数)20 (用户自定义)

3.总页数

总页数 = 100 / 20 = 数据总数 / 页面大小

页面数 = 103 / 20 = 数据总数 / 页面大小 + 1

–>

总页数 = 数据总数 % 页面大小 == 0 ? 数据总数 / 页面大小:数据总数 / 页面大小+1;

4.当前页(页码) (用户自定义)

5.当前页的对象集合(实体类集合):每页所显示的所有数据(10 个人信息)

List (查数据库,分页 sql)

// 分页帮助类
public class Page {
    // 当前页 currentPage
    private int currentPage;
    // 页面大小 pageSize
    private int pageSize;
    // 总数据 totalCount
    private int totalCount;
    // 总页数 totalPage
    private int totalPage;
    // 当前页的数据集合 students
    private List<Student> students;
    }

九、文件上传

1.上传

apache:从 https://mvnrepository.com 下载

commons-fileupload.jar 组件

commons-fileupload.jar 组件 依赖于 commons-io.jar

a.引入两个 jar 包

b.

代码:

前台 jsp:

<form action="UploadServlet" method="post" enctype="multipart/form-data">
上传照片:<input type="file" name="spicture"/>

表单提交方式必须是 post

在表单中必须增加一个属性 enctype=”multipart/form-data”

后台 servlet:

注意的问题:

上传到目录 upload;

1.如果修改代码,则在 tomcat 重新启动时 会被删除

原因:当修改代码的时候,tomcat 会重新编译一份 class 并且重新部署(重新创建各种目录)

2.如果不修改代码,则不会删除

原因:没有修改代码,class 仍然是之前的 class

因此,为了防止 上传目录丢失:a.虚拟路径 b.直接更换上传目录 到非 tomcat 目录

限制上传:类型、大小

类型:

String fileName = item.getName(); // a.txt  a.docx  a.png
String ext = fileName.substring(fileName.indexOf(".")+1);
if (!(ext.equals("png")||ext.equals("gif")||ext.equals("jpg"))) {
    System.out.println("图片类型有误! 格式只能是png gif jpg");
    return; // 终止
}

进行判断文件后缀名,如果不是指定照片格式,则终止。

大小:

DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);

// 设置文件上传时 用到的临时文件的大小(缓冲区)DiskFileItemFactory设置
factory.setSizeThreshold(10240); // 设置临时的缓存文件大小为10KB
factory.setRepository(new File("C:\\Users\\Wang\\Desktop\\uploadtemp")); // 设置临时文件的目录
// 控制上传单个文件的大小 20KB ServletFileUpload
upload.setSizeMax(20480); // 字节B

// 通过parseRequest解析form中的所有请求字段,并保存到item集合中(即前台传递到的sno sname spicture此时就保存在了item中)
                List<FileItem> items = upload.parseRequest(request);

注意:对文件的限制条件 写在 parseRequest 之前

2.下载

不需要依赖任何 jar 包

a.请求(地址 a form),请求 Servlet b.Servlet 通过文件的地址 将文件转为输入流 读到 Servlet 中

c.通过输出流 将 刚才 已经转为输入流的文件 输出给用户

注意:下载文件 需要设置 两个响应头

// 下载文件:需要设置  消息头
response.addHeader("contentType", "application/octet-stream"); // MIME类型:二进制文件
response.addHeader("content-Disposition", "attachement;filename="+fileName); // fileName包含了文件后缀:abc.txt

下载时,文件名乱码问题:

Edge 解决方法

URLEncoder.encode(fileName,"UTF-8")

FireFox、等其他浏览器需用 if 判断处理

// 对于不同浏览器,进行不同的处理
// 获取客户端的User-Agent信息
String agent = request.getHeader("User-Agent");
if (agent.toLowerCase().indexOf("edge") != -1) {
    // Edge下载 文件乱码问题
    response.addHeader("content-Disposition", "attachment;filename="+ URLEncoder.encode(fileName,"UTF-8")); // fileName包含了文件后缀:abc.txt
}

十、EL 表达式语言

EL:Expression Language,可以替代 JSP 页面中的 JAVA 代码

servlet(增加数据)-》jsp(显示数据)

传统的 在 JSP 中用 java 代码显示数据的弊端:类型转化、需要处理 null、代码参杂 -》EL

EL 操作符:

点操作符 . —使用方便

${requestScope.student.sno}
${域对象.域对象中的属性.属性.级联属性}

中括号操作符 [“ “] 或 **[‘ ‘] ** —功能强大:可以包含特殊字符(. 、-),获取变量值,获取数组的元素

${requestScope["student"]["sno"]}
${requestScope['student']['sno']}

当获取变量值时,不加引号,例如存在变量 name,则可以用

${requestScope[name]}

可以获取数组的元素,例如定义一个数组 String[] hobbies = new String[] {“football”,”pingpang”,”basketball”};

${requestScope.hobbies[0]}
${requestScope.hobbies[2]}

获取 map 属性

Map<String,Object> map = new HashMap<>();
map.put("cn", "中国");
map.put("us", "美国");
request.setAttribute("map", map);

关系运算符 逻辑运算符

${3>2}、${3 gt 2}<br/>				// 输出结果 true、true
${3>2 || 3<2 }、${3>2 or 3<2 }		// 输出结果 true、true

Empty 运算符:判断一个值 null、不存在 –> true

${empty requestScope["my-name"]}<br/>					 //输出结果	false
不存在的值:${empty requestScope["helloworld"]}<br/>		// 输出结果 true
为空的值:${empty requestScope.nullVal}<br/>				// 输出结果 true

EL 表达式的隐式对象:

隐式对象:(不需要 new 就能使用的对象,自带的对象)

a. 作用域的访问对象(EL 域对象):

pageScope、requestScope、sessionScope、applicationScope

如果不指定域对象,则会默认根据 从小到大的顺序 依次取值

b. 参数访问对象:获取表单数据(超链接中传的值、地址栏的值)

(request.getParameter()、 request.getParamterValues() )

${param} ${paramValues}

c. JSP 隐式对象:pageContext

在 JSP 中可以通过 pageContext 获取其他的 jsp 隐式对象;

因此如果要在 EL 中使用 JSP 隐式对象,就可以通过 pageCount 间接获取

可以使用此方法,级联获取对象

${pageContext.request.serverPort}

使用方法: ${pageContext.方法名去掉() 和 get 并且将首字母小写}

例如要拿 getRequset( ) ${pageContext.request}

十一、JSTL:

比 EL 更加强大,但需要两个 jar 包:jstl-1.2.jar、standard-1.1.2. jar

引入 tablib:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

其中 prefix=”c” :前缀

核心标签库:通用标签库、条件标签库、迭代标签库

a. 通用标签库:

< c:set> 赋值

  1. 在某个作用域之中(4 个范围对象),给某个变量赋值
<%--
    request.setAttribute("name", "zhangsan");
--%>
<c:set var="name" value="zhangsan" scope="request" />
${requestScope.name}

<c:set var=”变量名” value=”变量值” scope=”4 个范围对象的作用域” />

  1. 给普通对象赋值

    在某个作用域之中(4 个范围对象),给某个对象的属性赋值(此种方法,不能指定 scope 属性)

<c:set target=”${requestScope.student}” property=”sname” value=”zxs” />

给 map 对象赋值

<c:set target=”${requestScope.map}” property=”cn” value=”China” />

<c:set target=”对象” property=”对象的属性” value=”赋值” />

注意 < c:set> 可以给不存在的变量赋值(但不能给不存在的对象赋值)

< c:out>

  1. 显示(default=”当 value 为空时,显示的默认值”)
传统EL:${requestScope.student} <br/>
c:out方式<c:out value="${requestScope.student}" />  <br/>
c:out显示不存在的数据:<c:out value="${requestScope.stu}" default="不存在" />  <br/>
  1. 根据 escapexml 选择显示超链接方式
true:<c:out value='<a href="https://www.baidu.com">百度</a>' escapeXml="true" />
false:<c:out value='<a href="https://www.baidu.com">百度</a>' escapeXml="false" />

< c:remove> 删除属性

<c:remove var="a" scope="request" />

b. 条件标签库:选择

if(boolean)

单重选择

< c:if test=”” >

<c:if test="${10>2}" var="result" scope="request" >
    真
    ${requestScope.result}
</c:if>

多重选择

if else if… esle if ….

< c:choose >

< c:when test=”…”> < /c:when>

< c:when test=”…”> < /c:when>

< c:otherwise> < /c:otherwise>

< /c:choose >

注意:在使用 test=“” 一定要注意是否有空格

例如:test=“${10>2}” true

test=“${10>2} ” 非 true

c. 迭代标签库:循环

for(int i=0; i<5; i++ )

<c:forEach begin="0" end="5" step="1" varStatus="status">	// 0-5,varStatus显示下标次数
    ${status.index}
    test...
</c:forEach>

for(String str : names)

<c:forEach var="name" items="${requestScope.names}" >
    ${name}-
</c:forEach>

十二、过滤器与监听器

过滤器:

实现一个 Filter 接口

init()、destroy()原理、执行时机同 Servlet

配置过滤器,类似 Servlet

通过 doFilter()处理拦截,并且通过 chain.doFilter(request, response); 放行请求

public class MyFilter implements Filter { // 过滤器
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter...init...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("拦截请求。。。。。。");
        chain.doFilter(request, response); // 放行
        System.out.println("拦截响应。。。。。。");
    }

    @Override
    public void destroy() {
        System.out.println("filter...destroy...");
    }
}

filter 映射

只拦截 访问 Myservlet 的请求

<url-pattern>/MyServlet</url-pattern>

拦截一切请求(每一次访问,都会被拦截)

<url-pattern>/*</url-pattern>

通配符

dispatcher 请求方式:

REQUEST:拦截 HTTP 请求 get post 常用

FORWARD:只拦截 通过 请求转发方式的请求 常用

INCLUDE:只拦截通过 request.getRequestDispatcher(“”).include()、通过< jsp:include page=”…”/>发出的请求

ERROR:只拦截< error-page>发出的请求

过滤器中 doFilter 方法参数:ServletRequest

在 Servlet 中的方法参数:HttpServletRequest

过滤器链

可以配置多个过滤器,过滤器的先后顺序 是由 web.xml 中的位置决定的

监听器:

开发步骤:

  1. 编写监听器,实现接口
  2. 配置 web.xml

监听对象:request session application

a. 监听对象的创建和销毁

request:ServletRequestListener

session:HttpSessionListener

application:ServletContextListener

每个监听器 各自提供了 2 个方法:监听开始、监听结束的方法

b. 监听对象中属性的变更

request:ServletRequestAttributeListener

session:HttpSessionAttributeListener

application:ServletContextAttributeListener

每个监听器 各自提供了 3 个方法:新增、替换、删除属性

例如:ServletRequest

@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
    String attrName = srae.getName(); // 目前正在操作的属性名
    Object attrValue = srae.getServletRequest().getAttribute(attrName);
    System.out.println("ServletRequest【增加】属性:属性名"+attrName+",属性值:"+attrValue);
}

@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
    System.out.println("ServletRequest【删除】属性:属性名"+srae.getName());
}

@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
    String attrName = srae.getName();
    Object attrValue = srae.getServletRequest().getAttribute(attrName);
    System.out.println("ServletRequest【替换】属性:属性名"+attrName+",属性值:"+attrValue);
}

session 对象的四种状态:绑定解绑、钝化活化

监听绑定与解绑:HttpSessionBindingListener 不需要配置 web.xml

a. session.setAttribute(“a”,xxx) 对象 a【绑定】到 sesssion 中

b. session.removeAttribute(“a”)将对象 a 从 session 中【解绑】

监听 session 对象的钝化、活化:HttpSessionActivationListener 不需要配置 web.xml

c. 钝化:内存-》硬盘

d. 活化:硬盘-》活化

如何钝化、活化:配置 tomcat 安装目录/conf/context.xml

钝化、活化的本质 就是序列化、反序列化:序列化、反序列化需要实现 Serializable

总结:钝化、活化 实际执行 是通过 context.xml 中进行配置 而进行。

活化:session 中获取某一个对象时,如果该对象不存在,则直接尝试从之前钝化的文件中获取(活化)

HttpSessionActivationListener 只是负责 在 session 钝化和活化时 予以监听.

需要实现 Serializable

十三、Ajax,JQuery 与 JSON

Ajax:

异步 js 和 xml

异步刷新:如果网页中某个地方需要修改,异步刷新可以使:只刷新需要修改的地方,而页面中其他地方保持不变。

例如:百度搜索框、视频的点赞

实现:

js:XMLHttpReques 对象

XMLHttpReques 对象的方法:

open(方法名(提交方式 get|post),服务器地址,true):与服务端建立连接

send():

get:send(null)

post:send(参数值)

setRequestHeader(header,value):

get:不需要设置此方法

post:需要设置:

a. 如果请求元素中包含了 文件上传:

setRequestHeader(”Content-Type”,”multiparty/form-data”);

b. 不包含了 文件上传

setRequestHeader(”Content-Type”,”application/x-www-form-urlencoded”);

XMLHttpReques 对象的属性:

readyState:请求状态 只有状态为 4 代表请求完毕

status:响应状态 只有 200 代表响应正常

onreadystatechange:回调函数

responseTest:响应格式为 String

responseXML:响应格式为 XML

jquery:推荐

$.ajax({

url:服务器地址,

请求方式:get|post

data:请求数据,

success:function(result,testStatus)

{

},

error:function(xhr,errorMessage,e) {

}

});

<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" >
    function register() {
        var $mobile = $("#mobile").val();
        $.ajax({
            url:"MobileServlet",
            请求方式:"post",
            data:"mobile="+$mobile,
            success:function (result,testStatus) {
                if (result == "true") {
                    alert("已存在!注册失败!");
                } else {
                    alert("注册成功!");
                }
            },
            error:function (xhr,errorMessage,e) {
                alert("系统异常!");
            }
        })
    }
</script>

$.get(

服务器地址,

请求数据,

function(result) {
},

预期返回值类型(string\xml)

);

$.post(

服务器地址,

请求数据,

function(result) {
},

“xml” 或 “json” 或 “text”

);

十四、JNDI 与 tomcat 连接池

1. JNDI:

java 命名与目录接口

范围对象:4 大作用域对象、4 大范围对象

pageContext < request < session < application

pageContext:定义的变量有效期为当前页面,页面跳转后失效

request:一次请求有效

session:一次会话有效

application:一次项目运行期间都有效(项目打开后,只有不关闭,永远有效)

当我们需要多个项目都有效,除了数据库等,还可以使用 JDNI

JNDI:将某一个资源(对象),以配置文件(tomcat / conf / context.xml )的形式写入;

在配置文件(tomcat / conf / context.xml )中加入:

String jndiName = "jndiValue";

String:写在 type 中,需要写包,java.lang.String

jndiName:元素名写在 name 中

jndiValue:赋的值写在 value 中

2. JNDI 实现步骤:

tomcat / conf / context.xml 配置:

jsp 中使用:

<%
    Context ctx = new InitialContext();
    String textJndi = (String)ctx.lookup("java:comp/env/jndiName");

    out.print(textJndi);
%>

注意:java:comp/env/… (固定格式)

3. 连接池

常见的连接池:Tomcat-dbcp、dbcp、c3p0、druid

可以用 数据源 (javax.sql.DataSource)管理连接池 (数据源包含数据库连接池)

连接池:怎么用?

不用连接池

Class.forName();

Connection connection = DriverManager.getConnection();

使用连接池的核心:将连接的指向改了,现在指向的是数据源 而不是数据库

… -> DataSource ds = …..

Connection connection = ds.getConnection; // 指向的是数据源的连接

Tomcat-dbcp 连接池:

a. 类似 jndi,在 context.xml 中配置数据库,加入以下代码

<Resource name="student" auth="Container" type="javax.sql.DataSource" maxActive="400" maxIdle="20" maxWait="5000" username="root" password="159357" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/login" />

image-20220424173428003

b. 在项目 web.xml 中配置:

<resource-ref>
    <res-ref-name>student</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref>

c. 使用数据源

更改 连接对象 Connection 的获取方式:

传统 JDBC 方式:

connection = DriverManager.getConnection(URL, USER, PWD);

数据源方式:

Context ctx = new InitialContext(); // context.xml
ds = (DataSource)ctx.lookup("java:comp/env/student");
connection = ds.getConnection();

Tomcat - dbcp 数据源总结:

  1. 配置数据源(context.xml)
  2. 指定数据源(web.xml)
  3. 用数据源:通过数据源获取 Connection

dbcp 连接池:

需要引入 jar 包:commons-dbcp-1.4.jar、commons-pool-1.6.jar

BasicDataSource、BasicDataSourceFactory

BasicDataSource 方式:硬编码方式 BasicDataSource 对象设置各种属性

image-20220424215447606

// 获取dbcp方式的ds对象
public static DataSource getDataSourceWithDBCP() {
    BasicDataSource dbcp = new BasicDataSource();
    dbcp.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dbcp.setUrl("jdbc:mysql://localhost:3306/login");
    dbcp.setUsername("root");
    dbcp.setPassword("159357");
    dbcp.setInitialSize(20);
    dbcp.setMaxActive(10);

    return dbcp;
}

BasicDataSourceFactory 方式:配置方式(.properties 文件,编写方式 key = value ) 常用这种
dbcpconfig.properties

public static DataSource getDataSourceWithDBCPByProperties() throws Exception {
        DataSource dbcp = null;
        Properties props = new Properties();

        // 将文件变为数据流
        InputStream input = new DBCPDemo().getClass().getClassLoader().getResourceAsStream("dbcpconfig.properties");

        props.load(input); // 需要传入数据流

        // 只需要记住该行代码
        dbcp = BasicDataSourceFactory.createDataSource(props);
        return dbcp;
    }

DataSource 是所有 sql 是所有数据源的上级类。 BasicDataSource 是 dbcp 类型的数据源

ComboPooledDataSource 是 c3p0 的数据源;

c3p0 连接池:

核心类:ComboPooledDataSource

image-20220426002313113

两种方式:硬编码与配置文件

-》合二为一,通过 ComboPooledDataSource 的构造方法参数区分:如果无参——硬编码;有参——配置文件

jar 包:c3p0.jar

如果无参——硬编码

public static DataSource getDataSourceWithC3P0() throws PropertyVetoException {
    ComboPooledDataSource c3p0 = new ComboPooledDataSource();
    c3p0.setDriverClass("com.mysql.cj.jdbc.Driver");
    c3p0.setJdbcUrl("jdbc:mysql://localhost:3306/login");
    c3p0.setUser("root");
    c3p0.setPassword("159357");

    return c3p0;
}

有参——配置文件(c3p0-config.xml)文件

先写配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!-- 默认 -->
    <default-comfig>
        <property name="user">root</property>
        <property name="password">159357</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/login</property>
        <property name="checkoutTimeout">30000</property>
    </default-comfig>

    <named-config name="jun">
        <property name="user">root</property>
        <property name="password">159357</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/login</property>
    </named-config>

</c3p0-config>

在代码中传入参数,参数为中的 name:

public static DataSource getDataSourceWithC3P0ByXml() {
    ComboPooledDataSource c3p0 = new ComboPooledDataSource("jun");

    return c3p0;
}

总结:

所有连接池的思路:

  1. 硬编码,某个连接池数据源的 对象 ds = new XxxDataSource();

    ds.setXxx();

    return ds;

  2. 配置文件, ds = new XxxDataSource(); 加载配置文件,return ds;

数据源工具类:

可将 dbcp、c3p0 整合到一个类中,方便使用。

// 连接池帮助类
public class DataSourceUtil {

    // dbcp连接池
    public static DataSource getDataSourceWithDBCP() {
        BasicDataSource dbcp = new BasicDataSource();
        dbcp.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dbcp.setUrl("jdbc:mysql://localhost:3306/login");
        dbcp.setUsername("root");
        dbcp.setPassword("159357");
        dbcp.setInitialSize(20);
        dbcp.setMaxActive(10);

        return dbcp;
    }
    public static DataSource getDataSourceWithDBCPByProperties() throws Exception {
        DataSource dbcp = null;
        Properties props = new Properties();

        // 将文件变为数据流
        InputStream input = new DBCPDemo().getClass().getClassLoader().getResourceAsStream("dbcpconfig.properties");

        props.load(input); // 需要传入数据流

        // 只需要记住该行代码
        dbcp = BasicDataSourceFactory.createDataSource(props);
        return dbcp;
    }

    // c3p0连接池
    public static DataSource getDataSourceWithC3P0() throws PropertyVetoException {
        ComboPooledDataSource c3p0 = new ComboPooledDataSource();
        c3p0.setDriverClass("com.mysql.cj.jdbc.Driver");
        c3p0.setJdbcUrl("jdbc:mysql://localhost:3306/login");
        c3p0.setUser("root");
        c3p0.setPassword("159357");

        return c3p0;
    }

    public static DataSource getDataSourceWithC3P0ByXml() {
        ComboPooledDataSource c3p0 = new ComboPooledDataSource("jun");

        return c3p0;
    }
}

使用方法:

image-20220426005106728

十五、ApacheDBUtil

下载 commons-dbutils-1.7.jar,其中包含以下几个重点类:

DbUtils、QueryRunner、ResultSetHandler

1. DbUtils:辅助

包含了关闭连接、关闭结果、关闭提交、提交事务、传入驱动名,加载并注册 JDBC 驱动程序

2. QueryRunner:增删改、查

update()

query()

3. ResultSetHandler 的实现类

如果是查询,则需要 ResultSetHandler 接口,有很多实现类,一个实现类对应于一种 不同的查询结果类型

——Object[] {1,zs}

实现类ArrayHandler:返回结果集中的第一行数据,并用 Object[] 接收

实现类ArrayListHandler:返回结果集中的多行数据,List<Object[]>

——Student (1,zs)

BeanHandler:返回结果集中的第一行数据,用对象(Student)接收

// 查询单行数据(放入对象中)
public static void testBeanHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());
    Student Student = runner.query("select * from student where sno > ?", new BeanHandler<Student>(Student.class), 1);
    System.out.println(Student.getSno() + "," + Student.getSname());
}

反射会通过无参构造来创建对象

BeanListHandler:返回结果集中的多行数据,List students 接收

// 查询多行数据(放入对象中)
public static void testBeanListHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());
    List<Student> students = runner.query("select * from student where sno > ?", new BeanListHandler<Student>(Student.class), 1);
    for (Student student:students) {
        System.out.println(student.getSno() + "," + student.getSname());
    }
}

BeanMapHandler:可以给每个数据加个 key 1:stu , 2:stu12 , 3:stu3

// 查询单行数据(放入map对象中)
public static void testBeanMapHandler() throws SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());
    Map<Integer, Student> studentMap = runner.query("select * from student where sno > ?", new BeanMapHandler<Integer, Student>(Student.class, "sno"), 1);
    Student student = studentMap.get(5);
    System.out.println(student.getSno() + "," + student.getSname());
}

——Map 查询的结果为 {sno=1,sname=zs }

  1. MapHandler:返回结果集中的第一行数据

    {sno=1,sname=zs}

  2. MapListHandler:返回结果集中的多行数据

    sno=1,sname=zs sno=2,sname=ls

  3. KeyedHandler
    zs=sno=1,sname=zs,ls=sno=2,sname=ls

——

ColumListHandler:把结果集中的某一列 保存到 List 中

例如: 2,ls 3,ww

结果 {ls,ww}

ScalarHandler:单值结果集

问题:查询实现类的参数问题

query(…,Object…params )

其中 Object…params 代表可变参数:既可以写单值,也可以写数组

4. apache dbutil 增删改

增:

public static void add() throws PropertyVetoException, SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0());
    int count = runner.update("insert into student values(?,?,?,?)", new Object[]{11, "天天", 18, "北京"});
    System.out.println(count);
}

删:

public static void delete() throws PropertyVetoException, SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0());
    int count = runner.update("delete from student where sno = ? ", 10);
    System.out.println(count);
}

改:

public static void update() throws PropertyVetoException, SQLException {
    QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0());
    int count = runner.update("update student set sno = ? where sname = ? ", new Object[]{10,"天天"});
    System.out.println(count);
}

自动提交事务 update(sql,参数);update(sql);

手动提交事务 update(connection,sql,参数);

5. 手动提交事务

例如银行转账:zs-1000;li+1000; 需要一起提交

基础知识:

如果既要保存数据安全,又要保证性能,可以考虑ThreadLocal

ThreadLocal:可以为每个线程 复制一个副本。每个线程可以访问自己内部的副本。 别名 线程本地变量

set():给 tl 中存放一个 变量

get():给 tl 中获取变量(副本)

remove():删除副本

对于数据库来说,一个连接对应于一个事务,一个事务可以包含多个 DML 操作(增删改)

Service(多个原子操作)–》Dao(原子操作)

update:如果给每个 dao 操作 都创建一个 connection,则 多个 dao 操作对应于多个事务;

但是 一般来说,一个业务(Service)中的多个 dao 操作 应该包含在一个事务中。

——》解决:ThreadLocal,在第一次 dao 操作时,真正的创建一个 connnection 对象,然后

在其他几次 dao 操作时,借助于 ThreadLocal 本身特性,自动将该 connection 复制多个(connection 只创建了一个,因此该 connection 中的所有操作,必然对应于同一个事务;并且 ThreadLocal 将

connection 在使用层面复制了多个,因此可以同时完成多个 dao 操作)

事务流程:开始事务(将自动事务–>手动提交)–>进行各种 DML–>正常,将刚才所有 DML 全部提交(全部成功)

–>失败(异常),将刚才所有 DML 全部回滚(全部失败)

事务的操作 全部和连接 Connection 密切相关

十六、元数据

元数据:描述数据的数据

image-20220428234230282

三类:数据库元数据、参数元数据、结果集元数据

1. 数据库元数据:DataBaseMetaData

Connection——》DataBaseMetaData

public static void databaseMetaData() {
    try {
        Class.forName(DRIVER);
        Connection connection = DriverManager.getConnection(URL, USER, PWD);
        // 数据库元信息
        DatabaseMetaData dbMetadata = connection.getMetaData();

        String dbName = dbMetadata.getDatabaseProductName();
        System.out.println("数据库名:" + dbName);

        String dbVersion = dbMetadata.getDatabaseProductVersion();
        System.out.println("数据库版本:" + dbVersion);

        String driverName = dbMetadata.getDriverName();
        System.out.println(driverName);

        String url = dbMetadata.getURL();
        System.out.println(url);

        String userName = dbMetadata.getUserName();
        System.out.println(userName);

        System.out.println("------------------");

        ResultSet rs = dbMetadata.getPrimaryKeys(null, "ROOT", "STUDENT");
        while (rs.next()) {
            Object tableName = rs.getObject(3);
            Object columnName = rs.getObject(4);
            System.out.println(tableName + "--" + columnName);
        }

    } catch (Exception e) {
        e.printStackTrace();
    }
}

2. 参数元数据:ParameterMetaData

pstmt——》 ParameterMetaData

getParameterCount():获取 SQL 语句中,参数占位符的个数

因为带参数的 SQL 是通过 pstmt 执行的,因此需要从 pstmt 中获取参数元数据相关信息

public static void parameterMetaData() {
    try {
        Class.forName(DRIVER);
        Connection connection = DriverManager.getConnection(URL, USER, PWD);
        String sql = "select * from student where sno = ? and sage = ?";
        PreparedStatement pstmt = connection.prepareStatement(sql);
        // 通过pstmt获取参数元数据
        ParameterMetaData metaData = pstmt.getParameterMetaData();

        int count = metaData.getParameterCount();
        System.out.println("参数个数:" + count);
        for (int i = 1; i <= count; i++) {
            String typeName = metaData.getParameterTypeName(i);
            System.out.println(typeName);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

3. 结果集元数据:ResultSetMetaData

ResultSet——》 ResultSetMetaData

Mysql必须在 url 中附加参数配置:

jdbc:mysql://localhost:3306/数据库名?generateSimpleParameterMetadata=true

private static final String URL = "jdbc:mysql://localhost:3306/login?generateSimpleParameterMetadata=true";
public static void resultSetMetaData() {
    try {
        Class.forName(DRIVER);
        Connection connection = DriverManager.getConnection(URL, USER, PWD);
        String sql = "select * from student";
        PreparedStatement pstmt = connection.prepareStatement(sql);
        ResultSet rs = pstmt.executeQuery();
        ResultSetMetaData metaData = rs.getMetaData();

        int count = metaData.getColumnCount();
        System.out.println("列的个数:" + count);
        System.out.println("-------------");

        for (int i=1;i<=count;i++) {
            String columnName = metaData.getColumnName(i);

            String columnTypeName = metaData.getColumnTypeName(i);
            System.out.println(columnName+"\t"+columnTypeName);
        }
        while (rs.next()) {
            for (int i = 1; i<=count; i++) {
                System.out.print(rs.getObject(i) + "\t");
            }
            System.out.println();
        }

    } catch (Exception e) {
        e.printStackTrace();
    }
}

十七、自定义标签

验证码

防止恶意攻击

强制刷新:除了禁止缓存以外,还需要给服务端传递一个唯一的参数(没有实际用处)。随机数、时间

先通过 jsp 制作一个可以随机生成验证码图片

<%@ page import="java.awt.*" %>
<%@ page import="java.util.Random" %>
<%@ page import="java.awt.image.BufferedImage" %>
<%@ page import="javax.imageio.ImageIO" %>
<%@ page language="java" contentType="image/jpeg; charset=UTF-8" pageEncoding="UTF-8" %>

<%!
    // 随机产生颜色值
    public Color getColor() {
        Random ran = new Random();
        int r = ran.nextInt(256);// 产生0-255随机数
        int g = ran.nextInt(256);
        int b = ran.nextInt(256);
        return new Color(r,g,b); // red green blue 0-255
    }

    // 产生验证码值
    public String getNum() {
        // 0-8999   1000-9999
        int ran = (int)(Math.random() * 9000) + 1000;
        return String.valueOf(ran);
    }
%>

<%
    // 禁止缓存,防止验证码过期
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-control", "no-cache");
    response.setHeader("Expires", "0");

    // 绘制验证码
    BufferedImage image = new BufferedImage(80, 30, BufferedImage.TYPE_INT_RGB);
    // 画笔
    Graphics graphics = image.getGraphics();
    graphics.fillRect(0, 0, 80, 30);



    graphics.setFont(new Font("seif", Font.BOLD, 20));
    // 绘制验证码
    graphics.setColor(Color.BLACK);
    String checkCode = getNum();
    StringBuffer sb = new StringBuffer();
    for (int i=0;i<checkCode.length();i++) {
        sb.append(checkCode.charAt(i)+" "); // 验证码的每一位数字
    }

    graphics.drawString(sb.toString(), 15, 20); // 绘制验证码

    // 绘制干扰线条
    for (int i =0;i<30;i++) {
        Random ran = new Random();
        int xBegin = ran.nextInt(80);
        int yBegin = ran.nextInt(30);

        int xEnd = ran.nextInt(xBegin + 10);
        int yEnd = ran.nextInt(yBegin + 10);

        graphics.setColor(getColor());
        // 绘制线条
        graphics.drawLine(xBegin, yBegin, xEnd, yEnd);
    }

    // 将验证码真实值  保存在session中,供使用时比较真实性
    session.setAttribute("checkCode", checkCode);

    // 真实的产生图片
    ImageIO.write(image, "jpeg", response.getOutputStream());

    // 关闭
    out.clear();
    out = pageContext.pushBody();

%>

前台 jsp:

<html>
    <head>
        <title>验证码</title>
        <script type="text/javascript" src="js/jquery.js"></script>
        <script type="text/javascript">

            function reloadCheckImg() {
                $("img").attr("src","img.jsp?t="+(new Date().getTime()));
            }

            $(document).ready(function () {
                $("#checkCodeId").blur(function () {
                    var $checkCode = $("#checkCodeId").val();
                    // 校验:文本框中输入的值 发送到服务端
                    // 服务端:获取文本框输入的值,和真实验证码图片中的值对比,并返回验证结果
                    $.post(
                        "CheckCodeServlet",
                        "checkCode="+$checkCode,
                        function (result) { // 图片地址(imgs/right.jpg imgs/wrong.jpg)
                            var resultHtml = $("<img src='"+result+"' height='15' width='15'>")

                            $("#tip").html(resultHtml);
                        }
                    );
                });

            });
        </script>

    </head>

    <body>
        验证码:
        <input type="text" name="checkCode" id="checkCodeId" size="4" />
        <%-- 验证码 --%>
        <a href="javascript:reloadCheckImg();"><img src="img.jsp" /></a>
        <span id="tip"></span>
    </body>
</html>

以及 Servlet

public class CheckCodeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String resultTip = "imgs/wrong.jpg";
        // 获取用户输入的验证码
        String checkCodeClient = request.getParameter("checkCode");

        // 真实的验证码值
        String checkCodeServer = (String) request.getSession().getAttribute("checkCode");
        if (checkCodeServer.equals(checkCodeClient)) {
            resultTip = "imgs/right.jpg";
        }
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.write(resultTip);
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

JavaWeb
https://junyyds.top/2023/06/26/JavaWeb/
作者
Phils
发布于
2023年6月26日
更新于
2024年6月21日
许可协议