JavaWeb学习笔记

JavaWeb 学习笔记

1 基本概念

1.1 什么是 Web 开发

概念:Web 就是网页的意思,顾名思义,Web 开发指的就是网页开发

1.2 Web 开发分类

静态 Web:所有人可以看到的,始终不发生变化的数据,例如 HTML、CSS...

动态 Web:提供给所有人看,会发生变化的数据,每个人在不同时间、不同地点看到的是数据都是各不相同的

那么,在 Java 中,将开发动态 Web 的技术统称为 JavaWeb

1.3 Web 应用程序

概念:Web 应用程序,就是可以提供浏览器访问的程序

一个 Web 应用程序有多个部分组成:

  • HTML、CSS、JS

  • jsp、servelt

  • Java 程序

  • jar 包

  • 配置文件

一个 Web 应用程序编写完成后,若想提供给外界访问,就需要一个服务器来统一管理

1.4 静态 Web

以客户端打开 index.html 页面为例,客户端向服务器发起访问请求,Web Service 去服务器中找该页面,若服务器中若存在静态资源 index.html,则服务器会发送响应给客户端,这样客户端就可以打开该网页

image-20220406153039729

静态 Web 缺点

  • Web 页面无法实时更新,所有用户看到的都是同一个页面

  • 无法实现和数据库交互,数据无法持久化,用户无法交互

1.5 动态 Web

概念:动态 Web 是指,Web 数据显示的结果因人而异,不同的用户对应不同的数据显示结果

image-20220406155319443

动态 Web 缺点:如果加入服务器的动态 Web 资源出错,就需要重新编写后台程序,重新发布(网站停机维护)

动态 Web 优点

  • Web 页面可以实时动态刷新,所有用户看到的都是各不相同的页面及数据

  • 可以实现与数据库的交互(JDBC),可以实现数据的持久化

2 Web 服务器

2.1 什么是 Web 服务器

Web 服务器是一种被动的操作,用来处理用户的一些请求和给用户响应一些信息

2.2 Tomcat

image-20220407110055069

  • Tomcat 是 Apache 软件基金会的 Jakarta 项目中的一个核心项目,最新的 Servlet 和 JSP 规范总是能在 Tomcat 中得到体现

  • 技术先进、性能稳定,而且免费

  • Tomcat 属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试 JSP 程序的首选

  • Tomcat 实际上运行 JSP 页面和 Servlet

  • Tomcat 最新版本为 10.0.14

3 Tomcat

3.1 安装 Tomcat

首先,点击进入 Tomcat官网:Apache Tomcat® - Welcome!

选择 Tomcat 9,下载 windows 环境下的 64 位版本

image-20220407210053224

最后,将下载的压缩解压

3.2 Tomcat 启动和配置
3.2.1 启动和关闭

双击安装目录下的 startup.bat 文件,启动后,去浏览器打开 8080 端口,发现已经成功启动

image-20220407111927530

image-20220407111949299

双击运行安装目录下的 shutdown.bat 关闭应用

3.2.2 配置

修改端口号

  • Tomcat 的默认端口号为 8080端口

  • 安装路径下的 conf 目录下,可以通过修改 server.xml 文件来改变

    image-20220407113809256

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

修改主机名

  • Tomcat 默认主机名为 localhost

  • 同样可以通过修改 conf 目录下的 server.xml 来改变

  • 若修改此处的主机名之后,还要修改系统文件 C:/System32/drivers/etc/hosts 文件中的映射关系

<Host name="localhost"  appBase="webapps"
      unpackWARs="true" autoDeploy="true">

阿里高难度面试题:请你谈一谈网站是如何进行访问的?

  • 客户端输入一个域名后,点击搜索按钮

  • 首先会检查本机的 C:/System32/drivers/etc/hosts 文件是否有这个域名的映射

    • 有,直接返回对应的 ip 地址,在这个地址中,有我们需要访问的 Web 程序,可以直接进行访问

    • 没有,去 DNS 服务器中找,找到的话就返回,找不到就返回 404

image-20220407115406866

3.3 发布一个网站

自己写的网站,放入 Tomcat 服务器中指定的 Web 应用文件夹(webapps)之下,就可以进行访问了

|webapps		//	Tomcat 服务器的 web 目录
|———— ROOT
|———— MyWeb		//	自己网站的目录名
    |———— index.html	//	网站默认首页
	|———— WEB-INF
		|———— class			//	java 应用程序
		|———— lib			//	web 应用所依赖的 jar 包
		|———— web.xml		//	网站配置文件
    |———— static		//静态资源文件
        |———— css			//css 文件
        |———— js			//js 文件
        |———— img			//图片文件
    |———— ...			//其他资源

4 HTTP

4.1 什么是 http
  • http:超文本传输协议(Hyper Text Transfer Protocol,HTTP)是一个简单的请求 - 响应协议,它通常运行在 TCP 之上

    • 文本:字符串、HTML...

    • 超文本:图片、视频、音频、定位、地图...

    • 默认端口:80

  • https:全称(Hyper Text Transfer Protocol over SecureSocket Layer),是以安全为目标的 HTTP 通道,在 HTTP 的基础上通过传输加密和身份认证保证了传输过程的安全性

    • 默认端口:443
4.2 http 的两个时代
  • http 1.0 时代:客户端与 Web 服务器建立连接之后,只能获得一个 Web 资源,然后会断开链接

  • http 1.1 时代:客户端与 Web 服务器建立连接之后,可以获得多个 Web 资源

4.3 http 请求

以访问百度为例

  • 客户端 → 请求(Request)→ 服务器
General:
    Request URL: https://www.baidu.com/					//请求地址
    Request Method: GET									//请求方式(GET/POST)
    Status Code: 200 OK									//状态码
    Remote Address: 36.152.44.95:443					//远程地址(server + port)
    ...
Request Headers:
    Accept: text/html
    Accept-Encoding: gzip, deflate, br					//编码
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8			//语言
    Cache-Control: max-age=0							//缓存控制
    Connection: keep-alive								//链接保持
    Host: www.baidu.com									//主机名
    ...
4.3.1 请求行
  • 请求行中的请求方式:GET

  • 请求方式:Get、Post 、HEAD、GELETE、PUT、TRACT...

    • Get:请求可以携带的参数较少,大小有限制,会在 url 中显示数据内容,不安全但高效

    • Post:请求可以携带的参数无限制,大小无限制,不会在 url 中显示数据内容,安全但不高效

4.3.2 消息头
Accept-Encoding		//告诉浏览器,它所支持的数据类型
Accept-Language		//支持的编码格式(UTF-8、GN2312、GBK、ISO8859-1...)
Cache-Control		//缓存控制
Connection			//告诉浏览器,请求完成后是断开还是继续保持链接
Host				//主机名称
4.4 http 响应

以访问百度为例

  • 服务器 → 响应(Response)→ 客户端
General:
    Request URL: https://www.baidu.com/					//请求地址
    Request Method: GET									//请求方式(GET/POST)
    Status Code: 200 OK									//状态码
    Remote Address: 36.152.44.95:443					//远程地址(server + port)
    ...
Response Headers:
    Cache-Control: private								//缓存控制
    Connection: keep-alive								//链接保持
    Content-Encoding: gzip								//编码
    Content-Type: text/html;charset=utf-8				//类型
    ...
4.4.1 响应体
Accept-Encoding		//告诉浏览器,它所支持的数据类型
Accept-Language		//支持的编码格式(UTF-8、GN2312、GBK、ISO8859-1...)
Cache-Control		//缓存控制
Connection			//告诉浏览器,请求完成后是断开还是继续保持链接
Host				//主机名称
Refresh				//告诉客户端,多久刷新一次
Location			//让网页重新定位
4.4.2 响应状态码
  • 200:请求响应成功

  • 3xx:请求重定向

  • 4xx:找不到资源

  • 5xx:服务器出错

面试题:当在浏览器地址栏中输入地址并回车的一瞬间,直到页面被展示出来,经历了什么?

5 Maven

5.1 为什么使用 Maven

在 JavaWeb 开发中,需要使用大量的 jar 包,难道要去手动一个个导入吗?那效率也太低了,而且也很容易出错

那么与没有一种可以自动帮助导入 jar 包并且自动配置的工具呢?Maven 应运而生!

5.2 Maven 项目架构管理工具

Maven 的核心思想:约定大于配置! (有约束,就不要去违反)

Maven 会规定好我们应该如何去编写 Java 代码,必须按照这个规范来执行!

5.3 Maven 的下载

首先进入 Maven 官网 :Maven – Welcome to Apache Maven

按照自己的电脑系统,下载对应的压缩包

image-20220407174019080

下载完成后,解压得到如下目录

5.4 配置环境变量

我的 Maven 安装路径为:E:\PROGRAM_PATH\MAVEN\apache-maven-3.8.5

总共需要配置以下路径:

  • M2_HOME

    image-20220407195840631

  • MAVEN_HOME

    image-20220407195934010

  • 然后在 Path 中添加引用 %MAVEN_HOME\bin%

  • 在 cmd 命令行输入 mvn -version ,出现如下界面,表示添加成功

    image-20220407201040980

5.5 配置阿里镜像

为了加速下载,需要配置国内镜像,这里使用阿里云的镜像

在配置文件 E:\PROGRAM_PATH\MAVEN\apache-maven-3.8.5\conf\setting.xml 文件中找到 <mirrors> 标签,添加阿里云镜像

<mirror>
    <id>aliyunmaven</id>
    <mirrorOf>*</mirrorOf>
    <name>aliyun public repository</name>
    <url>https://maven.aliyun.com/repository/public</url>
</mirror>
5.6 创建本地仓库

这里我选择在 maven 安装路径的同级目录下创建一个 Maven 本地仓库

image-20220407202005480

同样,在上一步的配置文件中找到标签 <localRepository> ,添加本地仓库配置

<localRepository>E:/PROGRAM_PATH/MAVEN/localRepository/</localRepository>
5.7 项目配置 Maven

创建一个 Maven 项目有两种方式:采用默认的 Maven 模板和不使用模板,这是选择手动配置,不使用模板进行创建

在 IDEA 中左上角【File】→ 【New Project】,弹出创建项目界面

左边选择创建一个 Maven 项目,右边选择好 jdk 版本,然后点击【Next】

image-20220408154052807

填写项目名称,点击右下角的【Finish】

image-20220408154248251

出现如下界面,一个很清爽的 Maven 项目初步创建完成

image-20220408154423750

然后去 Maven Repository 找到自己需要的依赖粘贴复制进 pom 文件中, Maven 会帮助我们导入所有的依赖

5.8 创建并启动 JavaWeb 项目

首先需要使用 Maven 模板创建项目,步骤与 5.7 类似,唯一不同是需要勾选【create from archtype】,下方的模板选择【webapp】结尾的那个,然后点击【Next】

image-20220408161255225

接下来填写项目名称以及项目存放地址,点击【Next】

image-20220408161535693

点击右边的小三角,将 Maven 从 IDEA 自带的切换为我们自己的

image-20220408161713273

Maven 默认会选择电脑 C 盘目录下的 setting.xml 文件和本地仓库,这里应该更换为我们自己设置的,先点击右边的【Override】按钮,然后分别选择我们的 Maven 自定义安装目录下的 setting.xml 文件和本地仓库,然后点击【Finish】,等待项目创建成功

image-20220408161911072

然后点击主界面最右边的【Maven 工具栏】,依次点击【clean】、【compile】、【install】按钮,等待执行完成

image-20220408162444744

可以发现,模板默认为我们生成了一个 index.jsp 文件

image-20220408163142229

那么,怎么启动项目呢?因为是一个 JavaWeb 应用,所以需要 Tomcat 来充当服务器的角色,接下来需要配置 Tomcat

开始配置 Tomcat 服务器,点击主界面右上角的【Add Configuration】,添加 Tomcat 配置

image-20220408162637623

在弹出的界面中,选择左上角的【+】号,然后点击 Tomcat Server 栏目下的【Local】按钮

image-20220408154915014

在弹出的界面中,先给本项目的 Tomcat 命名,如 Tomcat 9,然后点击下方的【Configure】按钮选择 Tomcat 的安装路径以导入 Tomcat(一般会自动识别出来),然后检查一下 Tomcat 的启动端口是否正确(我设置的为 8088)

image-20220408165837185

在这里需要检查一下该页面的【URL】是否正确,观察项目结构可知,此处的 URL 应该定位到 webapp 文件夹,需要重新配置

image-20220408170229403

因此,手动添加 URL 路径

image-20220408170829225

接下来点击第二项【Deployment】,在该界面中,点击右边的【+】号,然后选择下方的【Exteral Source】按钮

image-20220408170946206

在弹出的界面中选择 webapp 所在的文件夹,然后点击【OK】

image-20220408171054510

回到原来的页面,依次点击【Aplly】、【OK】

image-20220408171133802

点击主界面右上角的启动按钮

image-20220408171231914

出现如下显示,说明项目成功启动

image-20220408171358754

成功启动后,IDEA 会自动打开浏览器(若没有自动打开,可以浏览器手动输入项目的 URL 地址进行访问),可以看到,一个最简单的 JavaWeb 项目创建并启动成功!

image-20220408171612124

5.9 pom 文件

创建好一个 Web 项目之后,会自动生成一个 pom 配置文件,该文件时 Maven 的核心配置文件,文件具体内容如下

<?xml version="1.0" encoding="UTF-8"?>

<!--Maven 版本和头文件-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--创建项目时配置的 GAV-->
    <groupId>org.example</groupId>
    <artifactId>com.jiuxiao.webapp</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--项目打包方式  jar 包: Java 应用 / war 包: JavaWeb 应用-->
    <packaging>war</packaging>

    <!--无关紧要的信息,删除也行-->
    <name>com.jiuxiao.webapp Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <!--配置-->
    <properties>
        <!--项目默认构建编码-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!--编码的版本-->
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <!--项目依赖-->
    <dependencies>
        <!--具体依赖的 jar 包配置文件-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!--项目构建用的东西-->
    <build>
        <finalName>com.jiuxiao.webapp</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

5.10 Maven 的小问题

由于 Maven 强调 “约定大于配置”,所以有可能会遇到我们自己写的配置文件,无法被导出或者失效的问题,因此我们需要去 bulid 中配置 resources:

<!--配置 resources,防止资源导入失败的问题-->
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <excludes>
                <exclude>**/*.properties</exclude>
                <exclude>**/*.xml</exclude>
            </excludes>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>
5.11 简单的 Servlet 程序
5.11.1 什么是 Servlet

Servlet(Server Applet)是 Java Servlet 的简称,称为小服务程序或服务连接器,用 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容

5.11.2 初识 Servlet

创建一个新的 JavaWeb 项目,新建测试类 HelloServlet ,该类需要继承 HttpServlet ,所以首先需要导入 Servlet 依赖

<!--Servlet依赖-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>

按照 Tomcat 自带的例子,重写 doGetdoPost 方法

package com.jiuxiao.servlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet测试
 *
 * @author WuDaoJiuXiao
 * @since 1.0.0
 */
public class HelloServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Servlet测试</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Servlet测试</h1>");
        out.println("</body>");
        out.println("</html>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

然后需要在 web.xml 中注册 Servlet 并建立映射

<display-name>Archetype Created Web Application</display-name>
<!--注册 Servlet-->
<servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.jiuxiao.servlet.HelloServlet</servlet-class>
</servlet>
<!--一个Servlet对应一个 mapping-->
<servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <!-- 设置请求路径-->
    <url-pattern>/jiuxiao</url-pattern>
</servlet-mapping>

新建一个测试用的静态网页文件 header.html

image-20220409174833510

启动 Tomcat,自动打开的仍然是原来的 index.jsp 页面,那么怎么打开我们自己创建的测试页面呢?只需要在网址后面加上 header.html 即可

image-20220409175126123

那么,怎么访问刚刚我们写的测试类 HelloServlet 类呢?

分析一下,在 web.xml 中我们已经将测试类 HelloServlet 进行了注册,然后给了它一个映射,映射路径 /jiuxiao ,所以,在地址栏中加上 /jiuxiao 后是否就能访问了呢?重启项目后访问,发现果然如我们所想,可以访问

image-20220409180256055

但是,出现了问题,为什么测试类中的 "测试" 两个字乱码呢?一般应该考虑编码的问题,网页右键检查元素,我们发现,响应头的默认编码格式为 Java 的默认编码格式 ISO-8859-1 ,而中文一般都是 UTF-8 编码,所以,是不是改变编码格式就行了呢?

image-20220409180431804

回到测试类 HelloServlet 中,设置编码格式为 UTF-8

@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    response.setContentType("text/html");
    response.setCharacterEncoding("UTF-8");	//设置编码为 UTF-8
    PrintWriter out = response.getWriter();
    out.println("<html>");
    out.println("<head>");
    out.println("<title>Servlet测试</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>Servlet测试</h1>");
    out.println("</body>");
    out.println("</html>");
}

然后重启 Tomcat,再次访问,发现已经可以正常访问,果然是编码问题

image-20220409181038561

6 Servlet

6.1 Servlet 简介

Servlet 就是 sun 公司开发的一种用来开发动态 Web 的技术

Sun 在这些 API 中提供一个接口叫做 Servlet,如果想开发一个 Servlet 程序,有以下两步骤

  • 编写一个类,实现 Servlet 接口

  • 把开发好的 Java 类部署到 Web 服务器中

其本质还是 Java 程序,只不过是实现了 Servlet 接口的 Java 程序

6.2 HelloServlet

创建一个普通的 Maven 项目(不适用模板),创建好之后删除 src 目录,只留下 .idea 文件夹、pom.xml 依赖文件以及 IDEA 识别项目类型的 servlet-text.iml 文件,此项目作为父工程,以后在里面创建子工程 Module ,只需要用父工程的 pom 文件进行管理即可,尽可能的将要使用的依赖导入到主工程中

image-20220410143436489

6.2.1 父子工程

在刚才的主工程 servlet-test 里面新建一个 Module 子工程 servlet-01 ,那么父与子项目的 pom 文件中会有如下区别

  • 父工程
<modules>
    <!-- servlet-01 就是子项目名称-->
    <module>servlet-01</module>
</modules>
  • 子工程
<parent>
    <!-- 子项目的 pom 文件中需要加入父工程的坐标-->
    <artifactId>javaweb-servlet</artifactId>
    <groupId>com.jiuxiao</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>

父项目中 jar 包子项目可以直接使用,反之,子项目中的 jar 包父项目不能使用

6.2.2 Maven 环境优化
  • 将子项目中的 web.xml 文件修改为最新版的配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="true">
</web-app>
  • 按照 Maven 的约束,将项目创建完整

    image-20220410161043870

6.2.3 创建 Servlet 程序

在子项目中创建测试类 Servlet 测试类,继承 HttpServlet

思考:前文 6.1 中已经提到,要编写 Servlet 程序,就需要实现 Servlet 接口,那么问题来了,为什么这里我们编写的测试类 HelloServlet 继承的是 HttpServlet 类,却不是前面提到的 Servlet 呢?

我们点进 HttpServlet 类,查看源码,发现 HttpServlet 继承了 GenericServlet

image-20220410152948401

再点进 GenericServlet 源码,发现它继承了 Servlet 接口

image-20220410153132020

再点进 Servlet 源码,发现他只有以下五个方法:

image-20220410153559124

其中初始化 init() 、获取配置 getServeltConfig()、getServletInfo() 、销毁 destory() 什么的都不重要,重要的是 service 接口,他有两个参数 ServletRequsetServletResponse 两个参数,这不正是我们测试类继承 HttpServlet 后重写 doGet()doPost() 两个主要方法时传进去的两个参数吗?

那么,我们追根溯源,逆着往回走,返回 GenericServlet 类,找到 service() 方法,发现他并没有具体去实现该方法

image-20220410153741910

继续往回走,回到 HttpServlet 类,我们发现,直到这里才具体实现了 service() 方法

image-20220410153920726

并且可以发现,该类已经具体实现了最重要的 doGet()、doPost() 方法

image-20220410154343355

但是,它实现的并不是我们需要的,因此需要我们继承之后对其进行重写!

继续往回走,回到我们的测试类 HelloServlet ,所以我们重写了 doGet()、doPost() 方法

image-20220410155709924

经过以上分析,我们明白了为什么要继承 HttpServlet 类,而不是 Servlet 类的原因,具体继承关系如下所示

image-20220410155422758

最终测试用的程序如下:

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 测试Servlet
 *
 * @author WuDaoJiuXiao
 * @since 1.0.0
 */
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.print("Hello, Servlet!");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
6.2.4 编写 Servlet 映射

我们编写的测试类,本质上仍然是 Java 程序,但是浏览器是不能直接访问 Java 程序的,浏览器只能访问 Web 服务器,所以需要将测试类注册到 Web 中,并且为其建立一个对应的路径来让浏览器可以访问它!

在 web.xml 中进行注册并且添加映射

<!--注册 Servlet-->
<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.jiuxiao.servlet.HelloServlet</servlet-class>
</servlet>
<!--映射 Servlet 路径-->
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>
6.2.5 配置 Tomcat

按照步骤 5.8 配置好 Tomcat 以及项目发布路径 URL

image-20220410161424139

6.2.6 启动项目

按照步骤 5.8 启动项目,发现可以成功访问,那么证明一个最简单的 Servlet 项目成功创建并运行

image-20220410163617461

这只是默认的 index.jsp 首页,想要访问我们的测试类 HelloServlet ,就需要加上我们当初配置好的映射路径,我这里为 /hello,加上后访问,发现也成功访问

image-20220410164037605

6.3 Servlet 原理

image-20220411145907011

6.4 Mapping 问题
  • 一个 Servlet 可以映射一个路径
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

image-20220411150746372

  • 一个 Servlet 可以映射多个路径
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello2</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello3</url-pattern>
</servlet-mapping>

image-20220411150854477

image-20220411150924419

  • 一个 Servlet 可以映射通用路径
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello/*</url-pattern>
</servlet-mapping>

image-20220411151041763

  • 默认请求路径
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

image-20220411151352356

直接进入了我们的测试类,没有进入默认的 index.jsp,所以不推荐这样映射

  • 可以指定前缀、后缀等
<!--可以自定义后缀实现请求映射	注意:* 前边不能加任何醒目映射的路径-->
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <!--报错,* 前边加了东西-->
    <url-pattern>/*.skg</url-pattern>
    <!--不会出错-->
    <url-pattern>*.skg</url-pattern>
</servlet-mapping>
6.5 映射路径的优先级

假设,我们有一个类 ErrorServlet 专门处理错误请求,如下:

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Servlet 错误处理
 *
 * @author WuDaoJiuXiao
 * @since 1.0.0
 */
public class ErrorServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html");
        PrintWriter writer = resp.getWriter();
        writer.print("<h2 align=\"center\">页面走丢了...</h2>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

然后去 web.xml 中注册

<!--Hello-->
<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.jiuxiao.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

<!--404-->
<servlet>
    <servlet-name>error</servlet-name>
    <servlet-class>com.jiuxiao.servlet.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>error</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

因为在 error 处理中,我们注册的路径是 /* ,那么,我们配置的另一个 hello 映射的路径是 /hello ,它也属于 /* 的一种,会不会被直接映射到 error 呢?

启动项目,发现初始页面为我们自定义的 error 映射,输入不存在的请求,也是返回 error 映射,说明 error 映射生效了

image-20220411153925214

image-20220411154244633

然后改变请求路径为 /hello ,发现可以正常访问,/hello 路径并没有被 /* 拦截

image-20220411154027712

因此,我们得出一个结论:如果指定了固有的映射路径,那么他的优先级最高,除非找不到,才会去走默认的处理请求

6.6 ServletContext
6.6.1 共享数据

Web 容器在启动的时候,它会为每个 Web 程序都创建一个对应的 ServletContext 对象

它代表当前所有的 Web 对象所对应的 Web 程序,共用一个 ServletContext 对象,也就是说,程序之间的数据可以相互之间共享

image-20220411170617443

创建设置信息类 HelloServlet 和获取信息类 GetServlet

package com.jiuxiao.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Servlet 设置信息
 *
 * @author WuDaoJiuXiao
 * @since 1.0.0
 */
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String username = "荒天帝";
        //将信息添加进 ServletContext 中
        context.setAttribute("username", username);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
package com.jiuxiao.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 获取 Servlet 信息
 *
 * @author WuDaoJiuXiao
 * @since 1.0.0
 */
public class GetServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        //从 ServletContext 中获取添加的信息
        String username = (String) context.getAttribute("username");

        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html");
        resp.getWriter().print("姓名:" + username);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

配置好两个类的映射路径

<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.jiuxiao.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>getc</servlet-name>
    <servlet-class>com.jiuxiao.servlet.GetServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>getc</servlet-name>
    <url-pattern>/getc</url-pattern>
</servlet-mapping>

启动项目,默认来到了 index.jsp 页面

输入获取类的映射路径 /getc ,访问,我们发现,获取到的 username 值为 null

image-20220411170933846

为什么呢?这是因为,此时,程序还没有走 HelloServlet 方法,那么 ServletContext 的总对象中也就没有数据,所以获取到的值为空,因此,要先设置属性值(先访问 /hello),然后再 /getc 获取数据

可以发现,获取到了数据,这样就实现了不同 Web 程序间的数据的共享

image-20220411171226719

程序执行的原理如下所示:

image-20220411173146571

6.6.2 获取初始化参数

创建一个获取初始化参数的测试类 GetParam

package com.jiuxiao.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 获取 ServletContext 初始化参数
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/12 15:58
 * @since 1.0.0
 */
public class GetParam extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String url = context.getInitParameter("mysql-url");
        resp.getWriter().print(url);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

配置对应的 Servlet 映射

<!--GetParam-->
<servlet>
    <servlet-name>gp</servlet-name>
    <servlet-class>com.jiuxiao.servlet.GetParam</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>gp</servlet-name>
    <url-pattern>/gp</url-pattern>
</servlet-mapping>

配置初始化参数,比如数据库的链接配置

<!--数据库连接配置-->
<context-param>
    <param-name>mysql-url</param-name>
    <param-value>jdbc:mysql://localhost:3306</param-value>
</context-param>

启动项目,初始化参数读取成功

image-20220412161039088

6.6.3 请求转发

创建一个请求转发的测试类 ServletDispatcher ,并注册路径,转发请求到 6.6.2 中的 /gp 页面中去

package com.jiuxiao.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 请求转发测试
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/12 16:41
 * @since 1.0.0
 */
public class ServletDispatcher extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        //context.getRequestDispatcher() 只是设置了请求转发的路径
        //调用 forward() 才会转发
        context.getRequestDispatcher("/gp").forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!--ServletDispatcher-->
<servlet>
    <servlet-name>sd</servlet-name>
    <servlet-class>com.jiuxiao.servlet.ServletDispatcher</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>sd</servlet-name>
    <url-pattern>/sd</url-pattern>
</servlet-mapping>

启动项目之后,我们访问 /sd 页面,发现经过他转发之后返回的页面仍然是 /gp ,也就是说,只是转发了请求,本质上还是请求的 /gp 页面(与重定向不同,重定向路径会发生改变,请求转发不改变路径)

image-20220412164844363

请求转发的原理如下:

image-20220412171057329

6.6.4 读取资源文件

在项目的 java/com/jiuxiao/servlet 目录下新建 db.properties 资源配置文件

username=skg
password=123456

注册 Servelt 路径映射

<!--ServletProperties-->
<servlet>
    <servlet-name>sp</servlet-name>
    <servlet-class>com.jiuxiao.servlet.ServletProperties</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>sp</servlet-name>
    <url-pattern>/sp</url-pattern>
</servlet-mapping>

使用 Maven 重新清理项目,再次启动项后后发现,两个配置文件都被生成在了统一目录下,即 classes 目录,我们俗称该路径为 classpath

image-20220412181027120

既然知道了配置资源的路径,那么就可以通 ServletContext 来读取资源文件的具体内容

新建读取资源测试类 ServletProperties

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 读取资源文件
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/12 17:22
 * @since 1.0.0
 */
public class ServletProperties extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
        Properties prop = new Properties();
        prop.load(is);
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        resp.getWriter().print("username :" + username + " " + "password : " + password);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

启动项目,成功读取到了资源文件得配置

image-20220412181632941

6.7 HttpServletResponse

Web 服务器接收到客户端的 http 请求,针对这个请求,分别创建一个代表该请求的 HttpServletRequest 对象,代表响应的一个 HttpServletResponse

  • 如果要获取客户端请求过来的参数,找 HttpServletRequest
  • 如果要响应给客户端一些数据,找 HttpServletResponse
6.7.1 方法分类
  • 负责向浏览器发送数据的方法
ServletOutputStream getOutputStream() throws IOException;

PrintWriter getWriter() throws IOException;
  • 负责向浏览器发送响应头的方法
void setCharacterEncoding(String var1);

void setContentLength(int var1);

void setContentLengthLong(long var1);

void setContentType(String var1);

void setBufferSize(int var1);

void setDateHeader(String var1, long var2);

void setHeader(String var1, String var2);

void setIntHeader(String var1, int var2);

void setStatus(int var1);
  • 响应的一些状态码
int SC_CONTINUE = 100;
int SC_SWITCHING_PROTOCOLS = 101;

int SC_OK = 200;
int SC_CREATED = 201;
int SC_ACCEPTED = 202;
int SC_NON_AUTHORITATIVE_INFORMATION = 203;
int SC_NO_CONTENT = 204;
int SC_RESET_CONTENT = 205;
int SC_PARTIAL_CONTENT = 206;

int SC_MULTIPLE_CHOICES = 300;
int SC_MOVED_PERMANENTLY = 301;
int SC_MOVED_TEMPORARILY = 302;
int SC_FOUND = 302;
int SC_SEE_OTHER = 303;
int SC_NOT_MODIFIED = 304;
int SC_USE_PROXY = 305;
int SC_TEMPORARY_REDIRECT = 307;

int SC_BAD_REQUEST = 400;
int SC_UNAUTHORIZED = 401;
int SC_PAYMENT_REQUIRED = 402;
int SC_FORBIDDEN = 403;
int SC_NOT_FOUND = 404;
int SC_METHOD_NOT_ALLOWED = 405;
int SC_NOT_ACCEPTABLE = 406;
int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
int SC_REQUEST_TIMEOUT = 408;
int SC_CONFLICT = 409;
int SC_GONE = 410;
int SC_LENGTH_REQUIRED = 411;
int SC_PRECONDITION_FAILED = 412;
int SC_REQUEST_ENTITY_TOO_LARGE = 413;
int SC_REQUEST_URI_TOO_LONG = 414;
int SC_UNSUPPORTED_MEDIA_TYPE = 415;
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
int SC_EXPECTATION_FAILED = 417;

int SC_INTERNAL_SERVER_ERROR = 500;
int SC_NOT_IMPLEMENTED = 501;
int SC_BAD_GATEWAY = 502;
int SC_SERVICE_UNAVAILABLE = 503;
int SC_GATEWAY_TIMEOUT = 504;
int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
6.7.2 实现重定向(重要)
  • 重定向原理:一个 Web 资源收到客户端索要资源的请求后,会通知客户端去访问另外一个 Web 资源,本质上改变了请求的路径,这个过程叫做重定向

image-20220413172108546

建立重定向测试类 RedirectServlet ,并在 Servlet 中注册,注册路径位 /red

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 重定向测试
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/13 17:28
 * @since 1.0.0
 */
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendRedirect("http://www.bilibili.com");
        
        //重定向本质就是程序帮我们设置了响应状态码和响应头
        //resp.setHeader("Location", "https://www.bilibili.com");
        //resp.setStatus(302);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!--RedirectServlet-->
<servlet>
    <servlet-name>redirect</servlet-name>
    <servlet-class>com.jiuxiao.servlet.RedirectServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>redirect</servlet-name>
    <url-pattern>/red</url-pattern>
</servlet-mapping>

启动项目,在地址栏的输入测试类的注册路径 /red

image-20220413173516593

然后点击回车,可以发现浏览器开始加载新页面,说明重定向有效果

image-20220413173604408

在调试窗口,找到响应头,检查其状态码发现是 302 ,代表重定向状态

image-20220413174417208

等待页面加载成功,成功来到了小破站,重定向成功

image-20220413173804026

6.7.3 重定向小例子

尝试实现如下功能

每次启动项目之后,打开的页面为默认的index.jsp 页面,我们修改该页面为简单的用户登录界面,点击提交之后,使用重定向,将页面跳转到 success.jsp 页面,表示用户登录成功

创建登录测试类 RequestTest ,注册路径为 /login

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录请求测试
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/13 20:02
 * @since 1.0.0
 */
public class RequestTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("用户登录成功");
        System.out.println(username + " : " + password);
        //重定向到 success.jsp 页面
        //这里的路径要注意,要加上项目启动的默认路径,比如本项目就是 /res
        resp.sendRedirect("/res/success.jsp");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!--RequestTest-->
<servlet>
    <servlet-name>login</servlet-name>
    <servlet-class>com.jiuxiao.servlet.RequestTest</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>login</servlet-name>
    <url-pattern>/login</url-pattern>
</servlet-mapping>

修改默认的项目启动页面 index.jsp 如下

<%--这里要设置浏览器的默认编码,否则登录页面会乱码--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<html>
<body>
<h2>用户登录</h2>
<%--这里的提交路径,需要寻找到项目的路径--%>
<%--${pageContext.request.contextPath} 代表当前路径--%>
<form action="${pageContext.request.contextPath}/login" method="post">
    用户名: <input type="text" name="username"><br>
    密&nbsp;&nbsp;&nbsp;&nbsp;码: <input type="password" name="password"><br>
    <input type="submit">
</form>
</body>
</html>

人为模仿一个登录成功的页面 success.jsp ,用户点击登录之后就要重定向到该页面

<%--
  Created by IntelliJ IDEA.
  User: YYH
  Date: 2022/4/13
  Time: 19:59
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Success</title>
</head>
<body>
<h2>Submit Success!</h2>
</body>
</html>

启动项目,默认启动界面为我们修改后的 index.jsp 页面

image-20220413201805202

我们输入用户名和密码,点击提交,成功跳转到我们设置的重定向页面 success.jsp

image-20220413201918834

返回控制台,控制台也成功打印了我们提交的用户信息,简单的重定向模拟登录的例子成功

image-20220413202022058

6.7.4 下载文件

新建下载文件测试类 FileServlet ,并且注册到 Servlet 中,注册路径为 /down

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;

/**
 * 下载文件
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/13 14:43
 * @since 1.0.0
 */
public class FileServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取要下载的文件链接
        String filePath = "E:\\MyCode\\test\\servlet-test\\response-01\\src\\main\\resources\\测试.png";
        System.out.println("下载文件的路径为 : " + filePath);
        //下载文件的文件名
        String fileName = filePath.substring(filePath.lastIndexOf("\\") + 1);
        //告诉浏览器我们需要下载的东西,中文文件名需要使用 URLEncoder.encode 编码
        resp.setHeader("Content-Disposition", "attachment;file=" + URLEncoder.encode(fileName, "UTF-8"));
        //获取下载文件的输入流
        FileInputStream in = new FileInputStream(filePath);
        //创建缓冲区
        int len = 0;
        byte[] buffer = new byte[1024];
        //获取 OutputStream 对象
        ServletOutputStream out = resp.getOutputStream();
        //将 FileOutputStream 流写入到缓冲区,使用 OutputStream 将缓冲区数据输出到客户端
        while ((len = in.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
        in.close();
        out.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!--FileServlet-->
<servlet>
    <servlet-name>fileDown</servlet-name>
    <servlet-class>com.jiuxiao.servlet.FileServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>fileDown</servlet-name>
    <url-pattern>/down</url-pattern>
</servlet-mapping>

启动项目,成功下载文件

image-20220413162451416

6.7.5 验证码实现

新建验证码测试类 ImageServlet ,并且注册 Servlet 路径,注册路径为 /code

package com.jiuxiao.servlet;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

/**
 * 验证码测试
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/13 16:34
 * @since 1.0.0
 */
public class ImageServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //让浏览器 3 秒刷新一次
        resp.setHeader("refresh", "3");

        //在内存中生成一张图片
        BufferedImage image = new BufferedImage(55, 20, BufferedImage.TYPE_INT_BGR);
        //创建画笔
        Graphics2D pen = (Graphics2D) image.getGraphics();
        pen.setColor(Color.WHITE);
        //图片填充背景色
        pen.fillRect(0, 0, 55, 20);
        //给图片写数据
        pen.setColor(Color.BLUE);
        pen.setFont(new Font(null, Font.BOLD, 20));
        pen.drawString(randomNums(), 0, 20);

        //告诉浏览器,该请求使用图片方式打开
        resp.setContentType("image/jpg");
        //网站存在缓存,禁用浏览器缓存
        resp.setDateHeader("expires", -1);
        resp.setHeader("Cache-Control", "no-cache");
        resp.setHeader("Pragma", "no-cache");

        //将图片写给浏览器
        ImageIO.write(image, "jpg", resp.getOutputStream());
    }

    //生成随机数
    private String randomNums() {
        Random random = new Random();
        String num = random.nextInt(9999) + "";
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 4 - num.length(); i++) {
            sb.append("0");
        }
        num += sb.toString();
        return num;
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!--ImageServlet-->
<servlet>
    <servlet-name>code</servlet-name>
    <servlet-class>com.jiuxiao.servlet.ImageServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>code</servlet-name>
    <url-pattern>/code</url-pattern>
</servlet-mapping>

启动项目,在浏览器中访问 /code 注册路径,发现验证阿妈生成成功,且每过 3 秒刷新一次

image-20220413171040585

image-20220413171050917

为什么能正确显示图片呢?检查响应头的信息,我们发现,在代码里设置的 Content-Type: image/jpg 生效了,所以浏览器才会将该请求识别为图片

image-20220413171702464

6.8 HttpServletRequest

HttpServletRequest 的方法几乎与 HttpServletResponse 相比差不多,只不过都是相对的,这不是很重要,不多赘述,HttpServletRequest 重要的应用一般有两种:获取前端参数和请求转发

6.8.1 获取参数与请求转发

HttpServletRequest 中常用的方法有以下三个:

  • 获取前端参数:getParameter()getParameterValues()
  • 请求转发:getRequestDispatcher().forward()

image-20220413203820384

新建测试类 LoginServlet ,注册 Servlet 路径为 /login

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

/**
 * 获取前端参数
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/13 20:47
 * @since 1.0.0
 */
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String[] hobbies = req.getParameterValues("hobbies");
        System.out.println("--------------");
        System.out.println(username + " : " + password);
        System.out.println("hobbies : " + Arrays.toString(hobbies));
        System.out.println("--------------");

        //请求转发到页面 success.jsp
        //这里若要把请求转发路径直接写死,需要注意 "/" 代表的已经是当前项目的默认路径
        //不能写成 /req/success.jsp,应该是 /success.jsp
        req.getRequestDispatcher("/success.jsp").forward(req, resp);
    }
}
<!--LoginServlet-->
<servlet>
    <servlet-name>login</servlet-name>
    <servlet-class>com.jiuxiao.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>login</servlet-name>
    <url-pattern>/login</url-pattern>
</servlet-mapping>

修改默认 index.jsp 为我们自定义的登录页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录页面</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login" method="post">
    用户名: <input type="text" name="username"><br>
    密&nbsp;&nbsp;&nbsp;&nbsp;码: <input type="password" name="password"><br>
    爱&nbsp;&nbsp;&nbsp;&nbsp;好:
    <input type="checkbox" name="hobbies" value="girl">女孩
    <input type="checkbox" name="hobbies" value="code">代码
    <input type="checkbox" name="hobbies" value="game">游戏
    <input type="checkbox" name="hobbies" value="movie">电影
    <br>
    <input type="submit">
</form>
</body>
</html>

模拟一个登陆成功页面 success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登陆成功</title>
</head>
<body>
<h2> Login Success!</h2>
</body>
</html>

启动项目,打开了我们自定义的主页

image-20220413212549854

填写信息,点击提交,成功跳转,实现了请求转发

image-20220413214645263

进入后台,后台已经打印出了前端的 HttpServletRequest 的相关信息,这就是获取前端的参数和请求转发

image-20220413214811612

6.8.2 转发与重定向区别(重要)

相同点:都会实现页面的跳转

不同点:

  • 请求转发, URL 不会产生变化,状态码为 307(参照 6.8.1)
  • 重定向,URL 会产生变化,状态码为 302(参照 6.7.3)

【重定向与请求转发辨析,重点!!!】

  • 对于 6.7.3 的例子,实现的是重定向, URL 会发生改变

先访问了 index.jsp 页面,点击提交后,再访问了 success.jsp 页面,整个过程中,客户端一共和两个页面发生了直接资源交换 ,所以本质上 URL 发生了改变

image-20220413220111587

  • 对于 6.8.1 的例子,实现的是请求转发,URL 不会发生改变

先访问了 index.jsp 页面,点击提交后,登录请求被 HttpServletRequest 通过 getRequestDispatcher().forward() 方法进行了转发,转发到了 success.jsp 页面,然后 success.jsp 页面将它自己页面的资源返回给了 index.jsp 页面,整个过程中客户端只与第一个页面 index.jsp 发生了直接资源交换 ,所以本质上 URL 没有发生改变

image-20220413221021928

7.1 什么是会话

会话:用户打开一个浏览器,点击了很多超链接,访问了多个 Web 资源,然后关闭了浏览器,这个过程可以称之为会话

思考:一个网站,怎么证明你来过?

  1. 服务端给客户端一个许可证,客户端下次访问服务端的时候带上许可证就可以了(Cookie)
  2. 服务端登记你曾经来过的信息,下次你来的时候,服务端匹配你的信息就可以了(Session)

有状态会话:一个同学来过教室,下次他再来这个教室,我们知道他曾经来过,这就称之为有状态会话

实现一个小 Demo:用户登录后,服务端保存用户的 Cookie 数据,关闭浏览器重新打开之后,用户仍然可以自动登录

新建测试类 CookieDemo01 ,注册路径为 cd1

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

/**
 * Cookie 测试
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/14 11:20
 * @since 1.0.0
 */
public class CookieDemo01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html");

        PrintWriter out = resp.getWriter();
        Cookie[] cookies = req.getCookies();

        //判断 Cookie 是否存在
        if (cookies != null) {
            out.write("你上次登录本站的时间为:");
            //获取 Cookie 内容
            for (int i = 0; i < cookies.length; i++) {
                Cookie cookie = cookies[i];
                if (cookie.getName().equals("lastLoginTime")) {
                    //获取 Cookie 中的值
                    long lastLoginTime = Long.parseLong(cookie.getValue());
                    Date date = new Date(lastLoginTime);
                    out.write(date.toLocaleString());
                }
            }
        } else {
            out.write("这是你第一次登录本站!");
        }

        //服务端给客户端响应一个 Cookie
        Cookie cookie = new Cookie("lastLoginTime", System.currentTimeMillis() + "");
        //设置 Cookie 有效期为一天
        cookie.setMaxAge(24 * 60 * 60);
        //响应头添加 Cookie
        resp.addCookie(cookie);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!--CookieDemo01-->
<servlet>
    <servlet-name>CookieDemo01</servlet-name>
    <servlet-class>com.jiuxiao.servlet.CookieDemo01</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>CookieDemo01</servlet-name>
    <url-pattern>/cd1</url-pattern>
</servlet-mapping>

启动项目,第一次登录时,登录时间为空,(因为是第一次登录,所以 Cookie 无数据)

我们刷新一次网页,会显示上次登录的时间,在响应信息里我们可以看到,里面记录了上次登录时间、 Cookie 的保存时间等信息

image-20220414115226721

关闭浏览器,然后再次访问,可以发现仍然保留着上次登录的时间

image-20220414120027971

  • 从请求中拿到 Cookie 信息

  • 服务器端给客户端响应 Cookie

Cookie[] cookies = req.getCookies();				//获取 Cookie 数组
cookie.getName();									//获取 Cookie 中的 Key
cookie.getValue();									//获取 Cookie 中的 Value
new Cookie();										//新建一个 Cookie
cookie.setMaxAge();									//设置 Cookie 的有效期
resp.addCookie();									//响应给客户端一个 Cookie
7.4 Session(重点)

什么是 Session:

  • 服务器会给每个用户创建一个 Session 对象
  • 一个 Session 独占一个浏览器,只要浏览器没有关闭,这个 Session 就存在
  • 登陆之后,整个网站的内容都可以访问

为什么浏览器一打开就会创建一个 Session 呢?

创建测试类 SessionDemo01 ,注册路径为 /sd1

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * Session 测试
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/15 14:07
 * @since 1.0.0
 */
public class SessionDemo01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        HttpSession session = req.getSession();

        session.setAttribute("username", "荒天帝");
        String sessionId = session.getId();
        if (session.isNew()) {
            resp.getWriter().write("Session 创建成功, ID 为 " + sessionId);
        } else {
            resp.getWriter().write("Session 已经在服务器中, ID 为 " + sessionId);
        }

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!--SessionDemo01-->
<servlet>
    <servlet-name>SessionDemo01</servlet-name>
    <servlet-class>com.jiuxiao.servlet.SessionDemo01</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>SessionDemo01</servlet-name>
    <url-pattern>/sd1</url-pattern>
</servlet-mapping>

启动项目,发现 Session 已经存在

image-20220415142708918

前边说过,只要浏览器一打开,Session 就已经创建成功,直至关闭浏览器,那么,系统到底进行了什么操作?

在浏览器中查看响应,我们给 Cookie 中并没有传参数,但是这里默认会有一个名为 JSESSIONID 的参数,而且该键对应的值刚好是我们所得到的 SessionId,两者之间是否有联系?

image-20220415143341397

我们很容易得到结论:在系统创建一个 Session 时,应该是帮我们做了以下的事情

// 创建了一个名为 JSESSIONID 的 Cookie
Cookie cookie = new Cookie("JSESSIONID", sessionId);
// 将创建的 Cookie 添加到响应体中
resp.addCookie(cookie);

我们发现,Session 对象设置的参数可以是一个对象,那么,设置一个对象之后,我们能否再通过 session 取到这个对象的值呢?

image-20220415144214635

创建一个测试对象 Student

package com.jiuxiao.pojo;

/**
 * Student 测试类
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/15 14:48
 * @since 1.0.0
 */
public class Student {
    private String name;
    private int age;
    private String city;

    public Student(String name, int age, String city) {
        this.name = name;
        this.age = age;
        this.city = city;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", city='" + city + '\'' +
                '}';
    }
}

将上面的 SessionDemo01 中的 setAttribute() 方法的入参由原来的字符串修改为 Student 对象

session.setAttribute("student", new Student("荒天帝", 500, "ShangHai"));   

创建获取 Session 对象信息的测试类 GetSessionInfo ,注册路径为 /gsi

package com.jiuxiao.servlet;

import com.jiuxiao.pojo.Student;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * 获取 Session 信息
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/15 14:44
 * @since 1.0.0
 */
public class GetSessionInfo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        HttpSession session = req.getSession();
        Student student = (Student) session.getAttribute("student");
        resp.getWriter().write(student.toString());
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!--GetSessionInfo-->
<servlet>
    <servlet-name>GetSessionInfo</servlet-name>
    <servlet-class>com.jiuxiao.servlet.GetSessionInfo</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>GetSessionInfo</servlet-name>
    <url-pattern>/gsi</url-pattern>
</servlet-mapping>

启动项目后,先访问 /sd1 将 Student 对象设置到 Session 中去,然后再访问 /gsi 获取对象信息,成功获取

image-20220415150027176

我们在 SessionDemo01 中向 Session 中添加了 Student 对象,而获取信息是在另外一个 Servlet 服务中获取的,这就实现了跨 Servlet 的资源共享

以前我们共享资源要使用 6.6.1 的 ServletContext 对象获取(不推荐),现在又可以使用 Session 进行获取(推荐)

使用了 Session 之后,怎么进行注销呢?

新建测试类 CloseSession ,注册路径为 /cs

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * 注销 Session
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/15 15:08
 * @since 1.0.0
 */
public class CloseSession extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        HttpSession session = req.getSession();

        //取消添加到 Session 的信息
        session.removeAttribute("student");
        //注销 Session
        session.invalidate();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!--CloseSession-->
<servlet>
    <servlet-name>CloseSession</servlet-name>
    <servlet-class>com.jiuxiao.servlet.CloseSession</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>CloseSession</servlet-name>
    <url-pattern>/cs</url-pattern>
</servlet-mapping>

我们先访问 /sd1 设置 Session 信息,然后访问 /cs 注销 Session,再次访问 /gsi 想要获取对象信息,发现 Session 已经被注销,报了个空指针异常

image-20220415151726252

这里我们注销 Session 采用的是手动注销方式,那么能否自动注销呢?当然可以,在 web.xml 中配置一下 Session 失效时间即可

<!--自动注销 Session-->
<session-config>
    <!--以分钟为单位-->
    <session-timeout>1440</session-timeout>
</session-config>

Session 使用场景

  • 保存一个用户的登录信息

  • 购物车信息

  • 在整个网站中经常要进行访问的数据,将其保存在 Session 中

  • Cookie 是把用户的数据写给用户的浏览器,浏览器进行保存(可以保存多个)

  • Session 对象由服务器创建,将用户的数据写入到用户独占的 Session 中,服务器进行保存(保存重要信息,减少服务器资源消耗)

image-20220415161445851

8 JSP

8.1 什么是 JSP

JSP 全称为 Java Servlet Pages:即 Java 服务器端页面,也和 Servlet 一样,用于动态 Web 技术

  • HTML 页面中只能为用户提供静态的数据
  • JSP 中可以嵌入 Java 代码,为用户提供动态数据
8.2 JSP 原理

JSP 看起来与 HTML 差不多,但是他执行原理是什么?凭什么可以支持动态数据响应?

我们在 IDEA中启动 Tomcat 服务,在控制台的输出信息中找到 Using CATALINA_BASE 一项,后面的那个路径就是我们项目的 JSP 文件编译后的路径

image-20220415163649095

复制后边的路径,打开电脑的资源管理器,地址栏粘贴后打开,可以看到有一个 work 目录,这就是项目文件的保存地址

image-20220415163453294

点击 work 目录,依次打开路径 work\Catalina\localhost\ROOT\org\apache\jsp\

我们神奇的发现,里面存放的居然是项目中 index.jspjava 文件和 java 程序编译后才会有的 class 文件!

image-20220415164126348

问题:所以,为什么呢?JSP 文件和 Java 的类到底有什么关联?值得深究

我们打开 index_jsp.java 文件,发现他继承了 HttpJspBase 父类,为了探究 JSP 原理,我们需要研究其父类

image-20220415165415202

我们需要在 IDEA 中手动导入父类的 jar 包

  • 依次点击【File】→【Project Structure】,在弹出的界面依次点击【Libraries】→【+】→【Java】

    image-20220415165632023

  • 然后在 Tomcat 的安装目录下的【lib】目录中,依次找到 先找到名为 jasper 的 jar 包,然后选择它 org 目录下的 runtime 包,点击【OK】,导入到当前项目中

    image-20220415170225567

将父类 jar 包导入之后,点开父类的源码,惊奇的发现, HttpJspBase 继承的父类,就是 HttpServlet

到此为止,一切疑问得到了解决,项目编译之后,JSP 会转化为一个 Servlet 对象!这也就是为什么 index.jsp 会转换为 java 文件!

image-20220415170853080

浏览器向服务器发送请求,不管访问的是什么资源,其本质都是在访问 Servlet!

JSP 说到底只是一种动态 Web 的技术,所以最终还是会被转换为一个 Java 类!

接下来,我们一步步分析 index_jsp.java 的源码,看看他都做了什么

我们发现,它里面有三个方法 Init()、destory()、service() ,分别对应着初始化、销毁和服务

image-20220415171619679

看起来有种似曾相识的感觉,这方法不就是 HttpServlet 的最基本方法吗?而且 _jspService() 方法的两个参数,不就是 6.2.3 中,我们耳熟能详的 HttpServletRequestHttpServletResponse

下面我们分析一下 Service() 方法都干了些什么事情

刚开始对请求进行了一些判断

image-20220415172203872

接下来初始化了一些内置对象

image-20220415172236541

final javax.servlet.jsp.PageContext pageContext;				// 页面上下文
javax.servlet.http.HttpSession session = null;					// Session 对象
final javax.servlet.ServletContext application;					// ApplicationContext 
final javax.servlet.ServletConfig config;						// Config 配置类
javax.servlet.jsp.JspWriter out = null;							// out 输出流
final java.lang.Object page = this;								// page = this 代表当前页
final javax.servlet.http.HttpServletRequest request;			// 请求
final javax.servlet.http.HttpServletResponse response;			// 响应

然后,在输出页面之前,又设置了一些参数

response.setContentType("text/html");							// 设置页面响应类型
pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;

分析至此,我们明白了 JSP 的工作原理,其本质就是一个 Servlet 对象,只不过替我们简化了一些渲染页面的方法

image-20220415174732716

在 JSP 页面中,只要是 Java 代码,都会原封不动的输出

如果是 HTML 代码,会被转换为 out.write("<h2>Hello</h2>") 形式输出到前端

8.3 JSP 基本语法

首先,将 JSP 相关的依赖导入

<dependencies>
    <!--Servlet 依赖-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
    </dependency>
    <!--JSP 依赖-->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
    </dependency>
    <!--JSTL 表达式依赖-->
    <dependency>
        <groupId>javax.servlet.jsp.jstl</groupId>
        <artifactId>jstl-api</artifactId>
        <version>1.2</version>
    </dependency>
    <!--standard 标签依赖-->
    <dependency>
        <groupId>taglibs</groupId>
        <artifactId>standard</artifactId>
        <version>1.1.2</version>
    </dependency>
</dependencies>

JSP 作为一种 Java 技术的应用,它支持 Java 的所有语法,除此之外还有一些自己所独有的语法

8.3.1 JSP 表达式
<%-- JSP表达式 : 一般用来输出结果到客户端 --%>
<%= new java.util.Date()%>

image-20220415203235425

8.3.2 JSP 脚本片段
<%-- JSP脚本片段 --%>
<%
    int sum = 1;
    for (int i = 0; i < 10; i++) {
        sum += i;
        out.print("</h2>" + sum + "</h2>&nbsp;&nbsp;");
    }
%>

image-20220415203245601

8.3.3 脚本嵌入 HTML 元素
<%--脚本嵌入 HTML 元素--%>
<%
    for (int i = 0; i < 5; i++) {
        Date obj = new Date();
        Thread.sleep(1000);
%>
<h2>
    Hello World Date : <%= obj.toLocaleString()%>
</h2>
<%
    }
%>

image-20220415204702048

8.3.4 JSP 声明
<%!
    static {
        System.out.println("Loading Servlet");
    }

    private int var = 0;

    public void test(){
        System.out.println("执行方法 test()...");
    }
%>

JSP 声明会被编译到 JSP 生成的 Java 的类中!其他的会被生成到 _jspService() 方法中

JSP 的注释不会在客户端显示,HTML 的会在客户端显示

8.4 JSP 指令(了解)

JSP 指令是指:程序出错跳转自定义页面、导入 Java 包等指令

<%-- 自定义错误页面 : 一般统一在 web.xml 中配置--%>
<%@ page errorPage="../error/500.jsp"%>

<%-- 导入 Java 包 --%>
<%@ page import="java.util.Date" %>

<%-- 导入网页公共页面: 头部、尾部 --%>
<%@ include file="common/header.jsp"%>		<%--导入方式1--%>

<%-- JSP 标签 --%>
<jsp:include page="500.jsp"/>				<%--导入方式2,区别在于路径前多一个斜杠--%>

创建一个自定义错误页面 my-error.jsp ,然后在测试页面中故意制造一个错误

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<img src="../image/500.jpg">
</body>
</html>

在测试页面故意制造一个错误

<%@ page errorPage="error/my-error.jsp" %>		<%-- 指令导入自定义页面方式 --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<%
    int num = 1/0;
%>
</body>
</html>

启动项目后,进入了我们的自定义错误页面

image-20220415211504991

一般情况下,不用在 jsp 页面中导入错误页面的设置,可以统一在 web.xml 中进行配置

<error-page>
    <error-code>404</error-code>
    <location>/error/404.jsp</location>
</error-page>
<error-page>
    <error-code>500</error-code>
    <location>/error/500.jsp</location>
</error-page>
8.5 九大内置对象
  • 存储信息功能的对象
    • pageContext : 保存的数据只在一个页面中有效
    • request : 保存的数据再一次请求中有效,请求转发也会携带该参数
    • session : 保存的数据在一次会话中有效,从浏览器打开到关闭
    • application : 即 ServletContext 保存的数据在服务器中有效,从服务器打开到关闭
  • 其他对象
    • config:即 ServletConfig
    • out
    • page:几乎不用
    • exception
8.6 JSP、JSTL、EL
8.6.1 JSP 标签

我们在页面 test.jsp 中将请求转发到 header.jsp 页面,并且在转发时携带参数

<%-- 转发到特定页面 --%>
<jsp:forward page="/header.jsp">
    <jsp:param name="name" value="Jack"/>
    <jsp:param name="age" value="18"/>
</jsp:forward>

然后在 header.jsp 中去获取参数

<h2>页面header</h2>
姓名: <%=request.getParameter("name")%>
年龄: <%=request.getParameter("age")%>

启动项目,访问 test.jsp 页面,使用 JSP 标签形式完成了转发和传参

image-20220416162038797

8.6.2 JSTL 表达式

JSTL 标签库的使用就是为了弥补 HTML 标签的不足,他可以自定义许多标签,标签的功能与 Java 代码功能一样

JSTL 标签库大致分为以下四种:核心标签、格式化标签、SQL 标签、XML 标签

JSTL 标签库的导入 <%@ taglib prefix="" uri=""%>

核心库常用标签

在 Tomcat 中也需要引入 JSTL 的标签库的 jar 包,否则会报错

  • <c:if> 标签
<form action="tag.jsp" method="get">
    用户名:<input type="text" name="username" value="${param.username}">
    密码:<input type="password" name="password" value="${param.password}">
    <input type="submit">
</form>

<%--其中 ${} 形式就是 EL 表达式--%>
<c:if test="${param.username == 'admin'}" var="isAdmin">
    <c:out value="欢迎管理员!"/>
</c:if>
<c:out value="${isAdmin}"/>

image-20220416165858152

  • <c:when>、<c:choose> 标签
<c:set var="score" value="88"/>
<c:choose>
    <c:when test="${score >= 80}">
        <c:out value="优秀"/>
    </c:when>
    <c:when test="${score >= 60}">
        <c:out value="一般"/>
    </c:when>
    <c:when test="${score < 60}">
        <c:out value="不及格"/>
    </c:when>
</c:choose>

image-20220417102100181

  • <c:foreach> 标签
<%
   List<String> people = new ArrayList<>();
   people.add("张三");
   people.add("李四");
   people.add("王五");
   request.setAttribute("list", people);
%>

%--不设置步长和起始位置--%>
<c:forEach var="people" items="${list}">
   <c:out value="${people}"/>
</c:forEach>

image-20220418091833273

<%
    List<String> people = new ArrayList<>();
    people.add(0, "张三");
    people.add(1, "李四");
    people.add(2, "王五");
    people.add(3, "赵六");
    people.add(4, "孙七");
    request.setAttribute("list", people);
%>
<%--设置步长和起始位置--%>
<c:forEach var="people" items="${list}" begin="1" end="4" step="2">
    <c:out value="${people}"/>
</c:forEach>

image-20220418092427146

8.7 JavaBean
8.7.1 JavaBean 写法
  • 必须要有一个无参构造

  • 属性必须私有化

  • 必须要有对应的 get/set 方法

  • 一般用来做数据库的字段映射

8.7.2 JSP 获取 Bean 信息

新建一个 Student 类,属性有 id、name、age、addreee

package com.jiuxiao.pojo;

/**
 * 学生类
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/18 10:32
 * @since 1.0.0
 */
public class Student {

    private int id;
    private String name;
    private int age;
    private String address;

    public Student() {
    }

    public Student(int id, String name, int age, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

然后使用 JSP 获取实体类信息

<jsp:useBean id="student" class="com.jiuxiao.pojo.Student" scope="page"/>

<jsp:setProperty name="student" property="id" value="1"/>
<jsp:setProperty name="student" property="name" value="荒天帝"/>
<jsp:setProperty name="student" property="age" value="18"/>
<jsp:setProperty name="student" property="address" value="荒域"/>

id:<jsp:getProperty name="student" property="id"/>
姓名:<jsp:getProperty name="student" property="name"/>
年龄:<jsp:getProperty name="student" property="age"/>
地址:<jsp:getProperty name="student" property="address"/>

image-20220418103842104

9 MVC 三层架构

9.1 什么是 MVC

MVC 分别为 Model、View、Controller 的简写,分别代表模型、视图、控制器

在该架构中,Servlet 应该专注于处理请求、响应、视图跳转,JSP 专注于显示数据

9.1.1 早期架构(两层)

image-20220418153701612

在早期的架构中,还没有第三层的 Model 层,在此架构中,用户可以直接访问控制层,控制层直接访问及操作数据库,这样做的话,会让程序十分臃肿,不利于维护

在此种架构模式中,Servelt 需要处理请求、响应、视图跳转、JDBC 链接、处理业务代码、处理逻辑代码等等,会让程序异常臃肿

9.1.2 MVC 架构(三层)

image-20220418161741134

Model 层

  • 业务处理:业务逻辑(Service)

  • 数据持久层:CURD(Dao)

View 层

  • 展示数据

  • 提供链接,发起 Servlet 请求

Controller 层

  • 接受用户请求

  • 交给业务层处理

  • 控制视图的跳转

10 Filter 和 Listener

10.1 过滤器 Filter

Filter:过滤器,顾名思义,有着过滤网站的数据的作用

image-20220418203645764

实现一个字符编码过滤器的 demo

  1. 创建一个 Maven 管理的 Web 项目,导入依赖
<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp.jstl</groupId>
        <artifactId>jstl-api</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>taglibs</groupId>
        <artifactId>standard</artifactId>
        <version>1.1.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
</dependencies>
  1. 我们新建一个 Servlet 服务,响应信息里如果有中文,那么为了页面显示不乱码,我们应该设置它的编码格式
package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 页面信息显示
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/18 20:54
 * @since 1.0.0
 */
public class ShowServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().print("你好,世界!");

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

测试类的注册路径如下,为了明显区别,我们注册两个路径,分别对比使用过滤器和不使用过滤器

<servlet>
    <servlet-name>showServlet</servlet-name>
    <servlet-class>com.jiuxiao.servlet.ShowServlet</servlet-class>
</servlet>
<!--走过滤器-->
<servlet-mapping>
    <servlet-name>showServlet</servlet-name>
    <url-pattern>/servlet/show</url-pattern>
</servlet-mapping>
<!--不走过滤器-->
<servlet-mapping>
    <servlet-name>showServlet</servlet-name>
    <url-pattern>/show</url-pattern>
</servlet-mapping>

那么问题来了,如果有成千上万个 Servelt 服务呢?难道一个一个设置编码格式?显然不太现实,这个时候,过滤器就出场了

  1. 继承 Filter 接口,重写方法
package com.jiuxiao.filter;

import javax.servlet.*;			// 此处的 Filter 类属于 javax.servlet.Filter
import java.io.IOException;

/**
 * 字符编码过滤器
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/18 20:44
 * @since 1.0.0
 */
public class CharacterEncodingFilter implements Filter {
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("过滤器初始化");
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("UTF-8");
        servletResponse.setCharacterEncoding("UTF-8");
        servletResponse.setContentType("text/html;charset=uft-8");
        System.out.println("过滤器执行之前...");
        // 只有执行了该句,请求和响应才会被放行,否则会被拦截
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("过滤器执行之后...");
    }

    public void destroy() {
        System.out.println("过滤器销毁");
    }
}

将过滤器进行注册,将 /servlet 之下的所有请求都进行过滤

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>com.jiuxiao.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <!-- 该配置表示 /servlet 之下的所有请求都要走过滤器 CharacterEncodingFilter  -->
    <url-pattern>/servlet/*</url-pattern>
</filter-mapping>

启动项目,我们发现,在服务器开始启动时,过滤器也会一并初始化完成,因为它要随时监听请求与响应

image-20220418211906293

我们先访问不使用过滤器的路径 /show ,字符果然是乱码

image-20220418212000170

在访问使用了过滤器的路径 /servlet/show ,经过过滤器,字符编码正常

image-20220418212211804

查看后台,可以看到走完了过滤器,字符编码问题也被解决

image-20220418212513753

然后我们停止项目,发现当服务器停止时,过滤器才会被销毁

image-20220418212633856

10.2 监听器 Listener

实现一个监听在线人数的监听器

创建监听器测试类 OnlineCountListener ,并在 web.xml 中进行注册

package com.jiuxiao.listener;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
 * 在线人数监听
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/19 9:57
 * @since 1.0.0
 */
public class OnlineCountListener implements HttpSessionListener {
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
        Integer onlineCount = (Integer) servletContext.getAttribute("onlineCount");

        if (onlineCount == null) onlineCount = 1;
        else onlineCount += 1;
        servletContext.setAttribute("onlineCount", onlineCount);
    }

    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
        Integer onlineCount = (Integer) servletContext.getAttribute("onlineCount");
		
        if (onlineCount == null) onlineCount = 0;
        else onlineCount -= 1;
        servletContext.setAttribute("onlineCount", onlineCount);
        httpSessionEvent.getSession().invalidate();     // 销毁监听器
    }
}
<listener>
    <listener-class>com.jiuxiao.listener.OnlineCountListener</listener-class>
</listener>

然后去前端页面取值

<h2>当前在线人数共 
    <span style="color: firebrick">
    <%=this.getServletConfig().getServletContext().getAttribute("onlineCount")%>
    </span>人
</h2>

启动项目,发现刚开始显示的为 3 人,这是 Tomcat 启动导致,重新发布项目,用两个浏览器模拟两个用户,人数显示正常

image-20220419103429809

10.3 应用场景

1. 监听器应用:在 GUI 中常用,Web 中几乎不用

2. 过滤器应用

实现用户登录功能,只有登录成功才能访问主页,否则不能访问

  1. 用户登录之后,将信息放入 Session
  2. 进入主页是要判断用户是否已经登录,过滤器中实现

用户登录类 LoginServlet ,注册路径为 /servlet/login

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/19 14:31
 * @since 1.0.0
 */
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        if (username.equals("admin")) {
            req.getSession().setAttribute("USER_SESSION", req.getSession().getId());
            resp.sendRedirect("/sys/success.jsp");
        } else {
            resp.sendRedirect("/sys/error.jsp");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

注销类 LogoutServlet ,注册路径位 /servlet/logout

package com.jiuxiao.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 注销登录
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/19 14:41
 * @since 1.0.0
 */
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Object user_session = req.getSession().getAttribute("USER_SESSION");
        if (user_session != null) {
            req.getSession().removeAttribute("USER_SESSION");
            resp.sendRedirect("/login.jsp");
        } else {
            resp.sendRedirect("/login.jsp");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

登录权限过滤器 SysFilter ,在 web.xml 注册

package com.jiuxiao.filter;

import javax.print.attribute.standard.NumberUp;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录权限过滤器
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/19 14:53
 * @since 1.0.0
 */
public class SysFilter implements Filter {
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;
        if (req.getSession().getAttribute("USER_SESSION") == null) {
            resp.sendRedirect("/sys/error.jsp");
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    public void destroy() {

    }
}

image-20220419152429505

11 JDBC

11.1 什么是 JDBC

JDBC:是一个统一的驱动接口,通过该接口可以实现不同数据库的链接,只针对 JDBC 接口编写程序,不必针对每个数据库编写

image-20220420100804364

11.2 JDBC 链接数据库
  1. 首先,建立一个简单的数据库 people ,创建一张测试表 student
CREATE TABLE `student` (
		`id` int(4) PRIMARY KEY,
		`name` VARCHAR(20),
		`age` int(8),
		`password` VARCHAR(20),
		`email` VARCHAR(40),
		`tel` VARCHAR(20)
);

INSERT INTO `student`(`id`, `name`, `age`, `password`, `email`, `tel`) VALUES
(1, '孙二', 18, '123456', 'se@qq.com', '8339564'),
(2, '张三', 24, '465456564', 'zs@qq.com', '8334654'),
(3, '李四', 35, 'sdfds45', 'ls@qq.com', '8356485'),
(4, '王五', 12, '453544', 'ww@qq.com', '8953456'),
(5, '赵六', 10, '4534533', 'zl@qq.com', '8775412');

创建好之后 student 表如下所示

image-20220420102818143

  1. 项目中导入数据库相关依赖
<dependencies>
    <!--Mysql驱动依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
</dependencies>
  1. 在 IDEA 中链接数据库发生时区错误的问题,在链接后边加上 ?serverTimezone=GMT%2B8 即可解决问题

image-20220420103757579

image-20220420103821779

image-20220420103904973

  1. 测试查询,成功输出查询结果
package com.jiuxiao.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * 数据库连接测试
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/20 10:42
 * @since 1.0.0
 */
public class JdbcTest {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:mysql://localhost:3306/people?useUnicode=true&characterEncoding=utf-8";
        String username = "root";
        String password = "0531";

        //1.加载驱动
        //mysql-connector-java 如果为 6.0 之上,需要用 com.mysql.cj.jdbc.Driver,之下为 com.mysql.jdbc.Driver
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.建立连接
        Connection connection = DriverManager.getConnection(url, username, password);
        //3.向数据库发送 SQL 的对象 statement
        Statement statement = connection.createStatement();
        //4.执行语句
        String sql = "select * from student where age > 16";
        ResultSet resultSet = statement.executeQuery(sql);
        //5.输出结果
        while (resultSet.next()) {
            System.out.println("id : " + resultSet.getObject("id") + "\t"
                    + "name : " + resultSet.getObject("name") + "\t"
                    + "age : " + resultSet.getObject("age") + "\t"
                    + "password : " + resultSet.getObject("password") + "\t"
                    + "email : " + resultSet.getObject("email") + "\t"
                    + "tel : " + resultSet.getObject("tel") + "\t"
            );
        }
        //6.关闭连接,释放资源(一定要做)
        resultSet.close();
        statement.close();
        connection.close();
    }
}

image-20220420111056622

11.3 预编译
package com.jiuxiao.jdbc;

import com.sun.corba.se.impl.resolver.SplitLocalResolverImpl;

import java.sql.*;

/**
 * 插入数据
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/20 11:24
 * @since 1.0.0
 */
public class JdbcTest02 {

    public static void main(String[] args) throws Exception {
        String url = "jdbc:mysql://localhost:3306/people?useUnicode=true&characterEncoding=utf-8";
        String username = "root";
        String password = "0531";

        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection(url, username, password);

        String sql = "insert into student(id, name, age, password, email, tel) value (?,?,?,?,?,?)";
        PreparedStatement prepareStatement = connection.prepareStatement(sql);
        prepareStatement.setInt(1, 6);
        prepareStatement.setString(2, "荒天帝");
        prepareStatement.setInt(3, 500);
        prepareStatement.setString(4, "112233");
        prepareStatement.setString(5, "htd@qq.com");
        prepareStatement.setString(6, "8415252");

        int i = prepareStatement.executeUpdate();
        if (i > 0) System.out.println("插入成功");

        prepareStatement.close();
        connection.close();
    }
}

image-20220420113154642

11.4 事务

事务的 ACID 性质:原子性、一致性、隔离性、持久性

junit 单元测试

在一个项目中测试方法时,可以引入 junit 包,直接使用注解 @Test 进行测试运行,该注解只能用在方法上

<!--junit依赖-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
public class JdbcTest03 {
    @Test
    public void test() {
        System.out.println("Hello World");
    }
} 

使用事务,模拟三个人转账

新建一个测试数据库

CREATE TABLE account(
			`id` INT PRIMARY KEY,
			`name` VARCHAR(30),
			`money` FLOAT
);

INSERT INTO account(`id`, `name`, `money`) VALUES
(1, 'A', 1000),
(2, 'B', 1000),
(3, 'C', 1000);

image-20220420141713912

下面使用代码模拟以下事务

start transaction ;
update account set money = money - 100 where name = 'A';
update account set money = money + 100 where name = 'B';
commit ;

若事务执行出错,需要执行回滚操作,保持数据库的一致性

package com.jiuxiao.jdbc;


import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * 事务
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/20 14:02
 * @since 1.0.0
 */
public class JdbcTest03 {

    @Test
    public void accountChange() {
        String url = "jdbc:mysql://localhost:3306/people?useUnicode=true&characterEncoding=utf-8";
        String username = "toot";
        String pwd = "0531";
        String sql01 = "update account set money = money - 100 where name = 'A'";
        String sql02 = "update account set money = money + 100 where name = 'B'";
        Connection connection = null;
        Statement statement = null;

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            connection = DriverManager.getConnection(url, username, pwd);
            
            connection.setAutoCommit(false);    // 通知数据库开启事务
            statement = connection.createStatement();
            statement.executeUpdate(sql01);
            statement.executeUpdate(sql02);
            
            //int num = 1/0;		//制造错误
            connection.commit();
            System.out.println("提交成功");
        } catch (Exception e) {
            try {
                assert connection != null;
                connection.rollback();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        } finally {
            try {
                assert statement != null;
                statement.close();
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

image-20220420144842226

posted @ 2022-04-20 15:03  悟道九霄  阅读(133)  评论(0编辑  收藏  举报