Java Web 开发基础

sky123

Java Web 总览

sequenceDiagram
    participant Client as 客户端
    participant WebServer as Java Web服务器
    participant Container as Web容器
    participant Listener as Listener
    participant Filter as Filter
    participant Servlet as Servlet

    Client->>WebServer: 发起请求
    activate WebServer

    WebServer->>Container: 处理请求
    activate Container
    
    Container->>Listener: 触发监听事件
    activate Listener

    Listener-->>Container: 监听事件处理完毕
    deactivate Listener

    Container->>Filter: 过滤请求内容
    activate Filter
    Note over Filter: 过滤器链

    Filter-->>Filter: 链式调用
    activate Filter

    Filter->>Servlet: 调用特定 Servlet
    activate Servlet

    Servlet-->>Filter: Servlet 处理完毕
    deactivate Servlet    

    Filter-->>Container: 过滤响应内容
    deactivate Filter

    Container-->>WebServer: 返回响应
    deactivate Container

    WebServer-->>Client: 返回响应
    deactivate WebServer

Java Web 组成

Web 服务器

  • 功能:Web服务器用于接收并处理客户端的 HTTP 请求,并将请求分发给合适的处理组件(如 Servlet)。常见的 Java Web 服务器包括 Apache Tomcat、Jetty 等。
  • 工作方式
    • 请求处理:服务器接受来自客户端的请求,分析请求路径和参数,将其分发给对应的 Servlet。
    • 资源管理:服务器负责管理应用程序的资源,如静态文件(HTML、CSS、JavaScript 等)、动态内容生成组件(Servlet、JSP等)。

前端相关

HTML/CSS/JavaScript

  • 功能:这些是 Web 页面的前端技术,用于构建和呈现用户界面:
    • HTML:定义网页的结构和内容。
    • CSS:用于美化和布局 HTML 页面,使其具有良好的外观和响应式设计。
    • JavaScript:用于在客户端实现动态交互和逻辑处理,如表单验证、动态内容更新等。

JSP(JavaServer Pages)

  • 功能:JSP 是一种基于Java的技术,用于快速生成动态 Web 页面。JSP 页面允许在 HTML 中嵌入 Java 代码,用于显示动态数据或处理用户输入。
  • 工作方式
    • 编译为 Servlet:每个 JSP 页面在第一次请求时都会被编译为一个 Servlet 类,并由 Servlet 容器管理。
    • 动态内容生成:JSP 页面可以通过 Java 代码生成动态内容,并在 HTML 中展示,如从数据库读取数据并显示在页面上。

后端相关

Servlet

  • 功能:Servlet 是 Java Web 应用的核心组件,用于处理来自客户端的请求并生成动态响应。Servlet 能够处理业务逻辑、与数据库交互、生成 HTML 或 JSON 响应等。
  • 工作方式
    • 初始化:当 Web 服务器启动或请求第一次到达时,Servlet 会被加载并初始化。
    • 处理请求:每次客户端发送请求时,Servlet 会调用相应的 doGet()doPost() 方法来处理请求,并生成响应。
    • 销毁:当服务器关闭或应用被卸载时,Servlet 会被销毁,释放资源。

Filter(过滤器)

  • 功能:Filter 是用于拦截和处理 HTTP 请求和响应的组件。它可以在请求到达 Servlet 之前或响应返回客户端之前执行一些通用的任务,如日志记录、身份验证、字符编码设置等。
  • 工作方式
    • 预处理请求:Filter 可以在请求到达 Servlet 之前,对请求进行检查或修改。
    • 后处理响应:Filter 可以在 Servlet 生成响应后,对响应进行修改或增强。
    • 链式处理:多个 Filter 可以形成一个链,按顺序依次处理请求和响应。

Listener(监听器)

  • 功能:Listener 用于监听 Web 应用中的特定事件(如应用启动、会话创建、请求处理等),并在事件发生时执行相应的操作。
  • 工作方式
    • 事件监听:Listener 会在特定事件(如应用启动或会话销毁)发生时被自动触发。
    • 资源管理:常用于初始化资源(如数据库连接池)或清理资源(如会话数据)。

数据库

  • 功能:数据库用于存储和管理应用程序的数据。Java Web 应用通常通过 JDBC 或其他持久化框架(如Hibernate)与数据库交互,执行数据的增删改查操作。
  • 工作方式
    • 数据持久化:应用程序可以通过Java代码与数据库进行交互,执行 SQL 语句,存储或检索数据。
    • 事务管理:数据库通常支持事务,确保数据操作的原子性和一致性。

MVC 模式和三层架构

MVC 模式

MVC(Model-View-Controller)模式是一种软件设计模式,广泛应用于 Web 开发中,用于分离应用程序的内部表示和用户界面。MVC 将应用程序分为三层:

  • M:模型层(Model),业务模型,处理业务
  • V:视图层(View),视图,界面展示
  • C:控制层(Controller),控制器,处理请求,调用模型和视图

这种模式的主要目的是实现关注点分离,使得应用程序的开发、测试和维护更加简单和高效。

三层架构

  • 数据访问层:对数据库的 CRUD 基本操作
  • 业务逻辑层:对业务逻辑进行封装,组合数据访问层中基本功能,形成复杂的业务逻辑功能。
  • 表现层:接收请求,封装数据,调用业务逻辑层,响应数据。

MVC 和 三层架构


可以将 MVC 模式理解成是一个大的概念,而三层架构是对 MVC 模式实现架构的思想。 那么我们以后按照要求将不同层的代码写在不同的包下,每一层里功能职责做到单一,将来如果将表现层的技术换掉,而业务逻辑层和数据访问层的代码不需要发生变化。

域对象

在 Java Web 开发中,”域对象”(Scope Object)是指用于在不同范围内存储和共享数据的对象。这些域对象提供了一种在不同组件之间传递数据的机制。

Java Web应用中常用的四大域对象是:

  1. PageContext(页面范围)
  2. HttpServletRequest(请求范围)
  3. HttpSession(会话范围)
  4. ServletContext(应用范围)

关于传输数据这里可以理解为存储全局变量(属性,以键值对的形式存在)的地方,只不过不同的域对象存活周期不同,也就是变量的作用域不同。通常域对象有如下三个方法来管理属性:

  • setAttribute(String name, Object value):设置属性
  • getAttribute(String name):获取属性
  • removeAttribute(String name):移除属性

PageContext(页面范围)

PageContext 对象在 JSP 页面中使用,表示页面范围的数据存储。它是生命周期最短的域对象,数据仅在当前页面内有效,无法在页面之间传递。

  • 作用范围:当前 JSP 页面。从页面开始处理到页面输出完成为止。
  • 使用场景
    • 在同一个 JSP 页面内共享数据,适合短暂的数据传递。
    • 用于存储仅在当前页面中使用的临时变量或对象。

HttpServletRequest(请求范围)

HttpServletRequest 对象表示请求范围的数据存储。数据在一次 HTTP 请求过程中有效,用于在请求期间在多个组件(如Servlet、JSP)之间传递数据。

  • 作用范围:当前 HTTP 请求。从请求到达服务器开始,到服务器处理完成并发送响应为止。
  • 使用场景
    • 在一次 HTTP 请求的过程中共享数据,例如在 Servlet 之间或 Servlet 与 JSP 之间传递数据。
    • 适合存储仅在一次请求期间有效的数据,如表单数据、查询参数等。

HttpSession(会话范围)

HttpSession 对象表示会话范围的数据存储。数据在用户的整个会话期间有效,用于在同一用户的多个请求之间共享数据。

  • 作用范围:用户会话。从会话创建(通常是用户第一次访问)到会话失效或销毁为止。
  • 使用场景
    • 在用户的整个会话期间共享数据,如用户的登录信息、购物车内容等。
    • 适合存储需要跨请求保存的用户状态信息。

ServletContext(应用范围)

ServletContext 对象表示应用范围的数据存储。数据在整个Web应用程序的生命周期内有效,用于在整个应用程序中共享数据。

  • 作用范围:整个Web应用。从应用程序启动到应用程序关闭为止。
  • 使用场景
    • 在整个应用程序中共享数据,如应用程序的配置信息、全局计数器等。
    • 适合存储全局性的数据,所有用户和请求都可以访问这些数据。

Maven Web 项目

Maven 是一个基于项目对象模型(POM,Project Object Model)的项目管理和构建工具,主要用于管理 Java 项目的依赖和构建过程。它提供了一套标准的项目结构、依赖管理和构建生命周期,旨在简化项目的构建、报告和文档过程。

Maven Web 项目结构

Maven Web 项目有一套标准的项目结构,从而使得项目在不同的开发环境下通用。

Maven Web 开发项目结构

一个典型的 Maven Web 项目目录结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
my-web-app/                      # 项目根目录,Maven 项目根目录
├── pom.xml # Maven 的配置文件,定义项目依赖、构建过程、插件等
└── src/ # 项目源码和资源文件
├── main/ # 主应用的源代码和资源目录
│ ├── java/ # Java 源代码目录
│ │ └── com/
│ │ └── example/
│ │ └── servlet/
│ │ └── HelloServlet.java # 示例 Servlet 类,处理 HTTP 请求
│ ├── resources/ # 资源文件目录(如配置文件、消息文件等)
│ │ └── config.properties # 应用程序的配置文件
│ └── webapp/ # Web 应用的资源目录(与 web 容器直接相关的资源)
│ ├── META-INF/ # 元数据目录(通常存放 MANIFEST.MF 文件,几乎不手动编辑)
│ ├── WEB-INF/ # Web 应用的私有目录,客户端无法直接访问
│ │ ├── web.xml # Web 应用的部署描述符,配置 Servlet、过滤器、监听器等
│ │ └── jsp/ # 存放 JSP 页面,防止直接通过 URL 访问
│ │ └── index.jsp
│ ├── css/ # 存放样式表文件的目录
│ │ └── styles.css
│ ├── js/ # 存放 JavaScript 文件的目录
│ │ └── scripts.js
│ └── images/ # 存放图片资源的目录
│ └── logo.png
└── test/ # 测试源码和资源目录
├── java/ # 测试代码目录
│ └── com/
│ └── example/
│ └── servlet/
│ └── HelloServletTest.java # 对 HelloServlet 的单元测试
└── resources/ # 测试资源文件目录
└── test-config.properties # 测试时使用的配置文件(如模拟环境配置)

构建后的 Web 应用目录结构

在 Maven Web 项目中,target 目录是 Maven 构建过程中生成的目录,包含了编译后的类文件、资源文件、测试文件,以及打包后的 WAR 文件。其中包后的 WAR 文件包含以下目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
my-web-app-1.0.0.war             # WAR 文件(解压后的目录结构)
├── META-INF/ # 元数据目录(JAR 和 WAR 的标准目录)
│ └── MANIFEST.MF # 清单文件,描述应用的基本信息
├── WEB-INF/ # Web 应用的私有目录(客户端无法直接访问)
│ ├── classes/ # 编译后的类文件和资源文件
│ │ ├── com/
│ │ │ └── example/
│ │ │ └── servlet/
│ │ │ └── HelloServlet.class # 编译后的 Servlet 类
│ │ └── config.properties # 从 src/main/resources/ 复制过来的配置文件
│ ├── lib/ # 项目依赖的 Jar 包,Maven 会根据 pom.xml 中的依赖配置,将所有的依赖 Jar 包复制到这个目录中。
│ │ ├── some-dependency-1.0.0.jar
│ │ ├── another-lib-2.0.0.jar
│ │ └── ... # 其他项目依赖的 Jar 包
│ ├── web.xml # 复制自 src/main/webapp/WEB-INF/web.xml
│ └── jsp/ # 存放 JSP 文件,避免直接通过 URL 访问
│ └── index.jsp
├── css/ # 复制自 src/main/webapp/css/
│ └── styles.css
├── js/ # 复制自 src/main/webapp/js/
│ └── scripts.js
└── images/ # 复制自 src/main/webappimages/
└── logo.png

pom.xml

pom.xml 是 Maven 项目的核心配置文件。POMProject Object Model 的缩写,它定义了一个 Maven 项目的基础信息、依赖关系、构建过程、插件等。

基本结构

一个典型的 pom.xml 文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<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> <!-- POM 模型的版本号,通常是 4.0.0 -->

<!-- 项目的基本信息 -->
<groupId>com.example</groupId> <!-- 项目的组织唯一标识(一般是公司域名的倒置) -->
<artifactId>my-web-app</artifactId> <!-- 项目的名称,构建产物的名称 -->
<version>1.0.0</version> <!-- 项目的版本号 -->
<packaging>war</packaging> <!-- 打包类型(jar、war、pom等),Web 项目通常是 war -->

<!-- 项目的名称和描述信息 -->
<name>My Web Application</name> <!-- 项目的名称(可选) -->
<description>A simple Java web application using Maven</description> <!-- 项目描述(可选) -->
<url>http://www.example.com</url> <!-- 项目主页(可选) -->

<!-- 项目依赖 -->
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope> <!-- 依赖范围,表示该依赖由容器提供,不会打包到 WAR 中 -->
</dependency>
</dependencies>

<!-- 构建配置信息 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- 指定 Java 源代码版本 -->
<target>1.8</target> <!-- 指定编译后的字节码版本 -->
</configuration>
</plugin>
</plugins>
</build>
</project>

dependencies

<dependencies> 元素用于定义项目的依赖库。<dependencys> 中可以放多个 <dependency>,每个 <dependency> 表示一个依赖,Maven 会自动从中央仓库或私有仓库下载这些依赖。在 IDEA 中我们可以使用 Alt+Insert 快捷键在 pom.xml 中添加依赖。

1
2
3
4
5
6
7
8
<dependencies>
<dependency>
<groupId>javax.servlet</groupId> <!-- 依赖库的组织标识 -->
<artifactId>javax.servlet-api</artifactId> <!-- 依赖库的名称 -->
<version>4.0.1</version> <!-- 依赖的版本号 -->
<scope>provided</scope> <!-- 依赖范围 -->
</dependency>
</dependencies>

其中 <scope> 元素在配置的时候需要格外注意:

  • **compile**(默认):编译时、测试时和运行时都需要的依赖,最终会被打包。
  • **provided**:编译和测试时需要,但运行时由容器提供,不会被打包(如 javax.servlet)。
  • **runtime**:运行和测试时需要,编译时不需要(如数据库驱动)。
  • **test**:仅测试时需要,不会被打包(如 JUnit)。

build

<build> 元素用于定义项目的构建过程,包括插件、编译配置等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- 指定 Java 源代码版本 -->
<target>1.8</target> <!-- 指定编译后的字节码版本 -->
<encoding>UTF-8</encoding> <!-- 源代码文件的编码格式 -->
</configuration>
</plugin>
</plugins>
</build>

properties

<properties> 元素用于定义 Maven 构建过程中的变量,方便在 pom.xml 中多次引用。

1
2
3
4
5
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
  • **<project.build.sourceEncoding>**:指定项目的编码格式。
  • <maven.compiler.source> 和 **<maven.compiler.target>**:指定编译源和目标 Java 版本。

IDEA中创建 Maven Web 项目

使用项目模板

不使用项目模板

Java Web 服务器

Java Web应用程序通常部署在Web服务器上,Web服务器处理客户端的HTTP请求,并将请求转发到Web应用程序进行处理。以下是一些常见的Java Web服务器环境:

Apache Tomcat

Apache Tomcat 是一个开源的 Web 服务器和 Servlet 容器,用于运行 Java Servlet 和 JavaServer Pages (JSP) 应用程序。它是 Java Web 开发中最常用的服务器之一。

安装和配置

  1. 下载 Apache Tomcat.

  2. 解压下载的文件。

  3. 将Web应用程序的WAR文件部署到 webapps 目录下。

  4. 启动Tomcat服务器,运行 bin/startup.sh(Linux/Unix)或 bin/startup.bat(Windows)。

    其中 windows 需要设置 JAVA_HOME 环境变量:

    1
    set JAVA_HOME=C:\Program Files\Java\jdk-21\

注意事项:

  • 控制台如果出现中文乱码,需要修改 conf/logging.propertiesjava.util.logging.ConsoleHandler.encoding = GBK

  • 关闭服务器:

    • 直接关掉运行窗口:强制关闭
    • bin\shutdown.bat:正常关闭
    • Ctrl+C:正常关闭
  • 修改启动端口号:conf/server.xml

    1
    2
    3
    4
    5
    <Connector port="8080" protocol="HTTP/1.1"
    connectionTimeout="20000"
    redirectPort="8443"
    maxParameterCount="1000"
    />

IDEA 中使用 Tomcat

使用安装的 Tomcat

在 IDEA 中配置 Tomcat 服务器,可以让 IDEA 自动操作 Tomcat 服务器完成项目部署。

使用 Maven Tomcat 插件

Maven 中的 Tomcat 插件主要用于将 Web 应用程序部署到嵌入式的 Tomcat 服务器,或是远程部署到已经运行的 Tomcat 服务器。这使得在开发、测试和持续集成过程中,无需手动操作服务器配置,便可以快速启动和测试 Web 应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/</path> <!-- 应用的上下文路径 -->
<port>8080</port> <!-- Tomcat 服务器的端口 -->
<uriEncoding>UTF-8</uriEncoding> <!-- 请求的编码设置 -->
<scanIntervalSeconds>10</scanIntervalSeconds> <!-- 热部署的扫描间隔时间(秒) -->
</configuration>
</plugin>
</plugins>

常见目录及文件

  • bin:可执行文件存放目录
    • startup.bat / startup.sh:启动 Tomcat 的脚本文件(分别用于 Windows 和Unix/Linux系统)。
    • shutdown.bat / shutdown.sh:关闭 Tomcat 的脚本文件(分别用于 Windows 和 Unix/Linux 系统)。
    • catalina.bat / catalina.sh:主要的启动脚本,提供了多种启动选项和命令。
    • tomcat-juli.jar:Tomcat 的日志管理工具。
  • conf:配置文件存放目录
    • server.xml:Tomcat 的主要配置文件,定义了服务器的基本设置、连接器、虚拟主机等。
    • web.xml:全局的 Web 应用程序部署描述文件,定义了 Servlet、过滤器、监听器等。
    • context.xml:默认的上下文配置文件,可以为每个 Web 应用程序定义单独的上下文。
    • tomcat-users.xml:定义 Tomcat 用户和角色的配置文件,主要用于管理控制台的用户认证。
    • logging.properties:日志配置文件,用于配置 Tomcat 的日志输出格式和级别。
  • lib:Tomcat 依赖的 jar 包
    • servlet-api.jar:Servlet API 的实现包。
    • jsp-api.jar:JSP API 的实现包。
    • el-api.jar:表达式语言(EL)的实现包。
    • 其他jar包:Tomcat 运行所需的各种库文件。
  • logs:日志文件
    • catalina.out:默认的 Tomcat 日志输出文件,包含 Tomcat 启动、运行时的信息和错误。
    • localhost_access_log.*.txt:访问日志文件,记录了每个 HTTP 请求的详细信息。
    • 其他日志文件:如 manager、host-manager 等应用的日志文件。
  • temp:Tomcat 在运行过程中生成的临时文件目录。Tomcat 会在启动时将某些数据存储在这里,并在关闭时清理这些数据。
  • webapps:应用发布目录
    • ROOT:默认的 Web 应用程序。
    • 其他 Web 应用:部署的 Web 应用程序目录或 WAR 文件。将 Web 应用程序的目录或 WAR 文件放在这个目录下,Tomcat 会自动解压和部署。
  • work:工作目录,Tomcat 在运行过程中生成的工作文件目录,包括编译后的 JSP 文件的 Servlet 类文件等。

Jetty

简介

Jetty是一个轻量级的开源Web服务器和Servlet容器,特别适用于嵌入式系统和云环境。它可以嵌入到Java应用程序中。

特点

  • 轻量级和高性能:适合需要快速响应和低资源消耗的应用。
  • 灵活:可以嵌入到Java应用中作为组件使用。
  • 支持WebSocket:适用于实时Web应用。

安装和配置

  1. 下载 Jetty.
  2. 解压下载的文件。
  3. 将Web应用程序的WAR文件部署到webapps目录下。
  4. 启动Jetty服务器,运行java -jar start.jar

Java 数据库相关

JDBC

JDBC(Java Database Connectivity)是 Java 中用于访问和操作关系型数据库的 API。它为开发者提供了一组标准接口,使得 Java 程序能够与各种数据库进行交互,而不必考虑具体数据库的底层实现。

JDBC 操作的基本步骤

以下是一个简单的 JDBC 示例代码,它展示了如何连接到数据库并执行 SQL 查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JDBCDemo {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1. 加载 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 2. 获取数据库连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");

// 3. 创建 Statement 对象
stmt = conn.createStatement();
String sql = "SELECT id, name FROM users";

// 4. 执行查询
rs = stmt.executeQuery(sql);

// 5. 处理查询结果
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.print("ID: " + id);
System.out.print(", Name: " + name);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 6. 关闭资源
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

加载驱动

在 JDBC 中,JVM 需要知道如何与特定类型的数据库进行通信。不同的数据库系统有不同的协议、命令和数据格式,因此需要通过数据库厂商提供的 JDBC 驱动程序来处理这些特定的细节。

每种数据库管理系统(DBMS)通常都会提供自己的 JDBC 驱动程序。这个驱动程序通常是一个或多个 JAR 文件,包含了该数据库的具体实现。

每种数据库管理系统的 JDBC 驱动程序都会实现 java.sql.Driver 接口,JDBC 通过这个接口的实现类与对应的数据库管理系统进行交互。例如 MySQL 的 JDBC 驱动类的完整类名为 com.mysql.cj.jdbc.Driver(旧版为 com.mysql.jdbc.Driver)。对应的 Maven 依赖为:

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>

DriverManager 是一个用于管理 JDBC 驱动的类,它维护了一个已注册的 Driver 实例的列表。当调用 DriverManager.getConnection() 方法时,DriverManager 会遍历这些已注册的驱动,并使用能够处理给定 JDBC URL 的驱动来建立连接。

在 JDBC 4.0(JDK 6)之前,驱动程序需要手动通过 Class.forName() 方法来加载和注册。

1
Class.forName("com.mysql.cj.jdbc.Driver");

这个方法会触发静态初始化块(static initializer)。在初始化块中,驱动类会通过 DriverManager.registerDriver() 方法将自身注册到 DriverManager 中。

1
2
3
4
5
6
7
static {
try {
java.sql.DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
} catch (SQLException e) {
e.printStackTrace();
}
}

从 JDBC 4.0 开始,Java 提供了一种自动加载和注册驱动程序的机制。这种机制基于 Java 的 SPI(服务提供者接口)框架,允许驱动程序在类路径中自动注册,无需显式调用 Class.forName()

SPI 是一种用于发现和加载服务实现的机制。在 JDBC 中,每个 JDBC 驱动程序都需要在其 JAR 文件的 META-INF/services/ 目录下创建一个名为 java.sql.Driver 的文件。这个文件包含了驱动程序实现类的全限定类名。

当 JVM 启动时或首次调用 DriverManager.getConnection() 时,DriverManager 会通过 ServiceLoader 去查找所有在类路径中的 JAR 文件中 META-INF/services/java.sql.Driver 文件。它会读取这些文件中的类名,并自动加载这些类。

建立连接

通过 DriverManager 类的 getConnection() 方法来创建与数据库的连接。

1
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");
  • jdbc :表示使用 JDBC 协议。
  • mysql :数据库类型或驱动类型,例如 mysqlpostgresqloracle 等。
  • localhost :数据库服务器的主机名或 IP 地址。
  • 3306 :数据库服务器的端口号,通常是数据库的默认端口号,例如 MySQL 的默认端口是 3306
  • mydatabase :要连接的数据库的名称。

提示

如果连接的是本机 mysql 服务器,并且 mysql 服务默认端口是 3306,则 url 可以简写为 jdbc:mysql:///数据库名称

在实际开发中,与数据库建立连接的相关信息一般都存放于 resources 目录下的配置文件中,这样就不需要在修改数据库配置的情况下重新编译项目。

首先我们可以在 src/main/resources/ 目录下创建一个名为 db.properties 的配置文件,用于存放数据库连接的相关信息:

1
2
3
4
5
# src/main/resources/db.properties
jdbc.url=jdbc:mysql://localhost:3306/mydatabase
jdbc.username=username
jdbc.password=password
jdbc.driverClassName=com.mysql.cj.jdbc.Driver

在 Java 代码中,通过以下方式读取这个配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建 Properties 对象,用于加载配置文件
Properties properties = new Properties();

// 读取配置文件(从类路径中)
InputStream input = DatabaseConnection.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(input);

// 获取配置文件中的值
String url = properties.getProperty("jdbc.url");
String user = properti es.getProperty("jdbc.username");
String password = properties.getProperty("jdbc.password");
String driverClassName = properties.getProperty("jdbc.driverClassName");

创建 Statement 对象

StatementPreparedStatement 是 JDBC API 中用于执行 SQL 语句的两个主要接口。

Statement 对象用于执行不带参数的简单 SQL 语句。它通常用于执行静态 SQL 查询,SQL 语句在每次执行时都是重新解析和编译的。如果通过字符串拼接实现带参数查询容易造成 SQL 注入:

1
2
3
Statement stmt = conn.createStatement();
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
ResultSet rs = stmt.executeQuery(sql);

PreparedStatement 对象用于执行带参数的 SQL 语句。与 Statement 不同的是,PreparedStatement 允许使用占位符(?)在 SQL 语句中代表参数,然后通过设置这些参数来执行 SQL 语句。由于 PreparedStatement 使用参数化查询,它能够自动对传入的参数进行转义和处理,因此能够有效防止 SQL 注入攻击

1
2
3
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users WHERE username = ?");
pstmt.setString(1, username); // 通过参数设置 SQL 语句中的占位符
ResultSet rs = pstmt.executeQuery();

PreparedStatement 另外一个好处是 SQL 语句在 PreparedStatement 创建时就已经预编译了。执行相同的 SQL 语句多次时,数据库不需要重新解析和编译,从而提高了性能。

例如批处理操作(batch processing)通过 addBatch() 方法将多个 SQL 语句添加到批处理中,然后通过 executeBatch() 一次性执行。

PreparedStatement 的 SQL 语句预编译一次,批处理时只需传递参数,效率更高。

1
2
3
4
5
6
7
8
9
10
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users (username, password) VALUES (?, ?)");
pstmt.setString(1, "user1");
pstmt.setString(2, "pass1");
pstmt.addBatch();

pstmt.setString(1, "user2");
pstmt.setString(2, "pass2");
pstmt.addBatch();

pstmt.executeBatch();

Statement 每次执行时都需要重新编译 SQL 语句,批处理时效率较低。

1
2
3
4
5
6
7
stmt = conn.createStatement();

stmt.addBatch("INSERT INTO users (username, password) VALUES ('user1', 'pass1')");
stmt.addBatch("INSERT INTO users (username, password) VALUES ('user2', 'pass2')");
stmt.addBatch("INSERT INTO users (username, password) VALUES ('user3', 'pass3')");

int[] updateCounts = stmt.executeBatch();

执行查询或更新

StatementPreparedStatement 执行 SQL 语句主要有 executeQueryexecuteUpdate 两个方法。

  • ResultSet executeQuery(String sql)

    • executeQuery 只能用于 SELECT 查询语句,不能用于 INSERTUPDATEDELETE 或 DDL(如 CREATE TABLE)语句。
    • 返回 ResultSet 对象,用于遍历查询结果的每一行数据(通过 nextgetXxx 方法)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    String sql = "SELECT id, name FROM users WHERE age > 30";
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(sql);

    while (rs.next()) {
    int id = rs.getInt("id");
    String name = rs.getString("name");
    System.out.println("ID: " + id + ", Name: " + name);
    }

    rs.close();
    stmt.close();
  • executeUpdate(String sql)

    • 用于执行 INSERTUPDATEDELETE 等更新语句,以及 DDL 语句(如 CREATE TABLEALTER TABLEDROP TABLE)。
    • 返回一个整数,表示受影响的行数(对于 DDL 语句,通常返回 0)。
    1
    2
    3
    4
    5
    6
    String sql = "UPDATE users SET name = 'John' WHERE id = 1";
    Statement stmt = conn.createStatement();
    int rowsAffected = stmt.executeUpdate(sql);
    System.out.println("Rows affected: " + rowsAffected);

    stmt.close();

关闭资源

操作完成后,关闭 ResultSetStatementConnection 对象以释放数据库资源:

1
2
3
rs.close();
stmt.close();
conn.close();

JDBC 高级特性

事务管理

事务(Transaction)是指一个或多个 SQL 操作的集合,这些操作要么全部成功提交(Commit),要么全部回滚(Rollback)以恢复到操作之前的状态。事务有四个关键属性,通常称为 ACID 属性:

  • Atomicity(原子性):事务中的所有操作要么全部完成,要么全部回滚。事务不可分割。
  • Consistency(一致性):事务的执行必须使数据库从一个一致状态转换到另一个一致状态。在一致状态下,所有数据都满足数据库的完整性约束。
  • Isolation(隔离性):在事务执行过程中,其操作对其他事务是隔离的。不同事务之间不会相互干扰。
  • Durability(持久性):一旦事务提交,结果就被永久保存,即使系统崩溃也不会丢失数据。

JDBC 中失误管理的基本操作步骤如下:

  1. 关闭自动提交:默认情况下,每个 SQL 语句在执行后都会自动提交(即自动执行 COMMIT 操作)。我们可以通过 conn.setAutoCommit(false) 来关闭自动提交模式。
  2. 执行 SQL 操作:使用 StatementPreparedStatement 对象执行一系列的 SQL 操作。
  3. 提交事务:如果所有操作成功,通过 conn.commit() 提交事务。
  4. 回滚事务:如果有任何操作失败,通过 conn.rollback() 回滚事务,将数据库状态恢复到事务开始之前的状态。

以下是一个使用 JDBC 进行事务管理的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class TransactionExample {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;

try {
// 1. 获取数据库连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");

// 2. 关闭自动提交模式
conn.setAutoCommit(false);

// 3. 创建 Statement 对象
stmt = conn.createStatement();

// 4. 执行一组 SQL 操作
stmt.executeUpdate("INSERT INTO accounts (account_id, balance) VALUES (1, 1000)");
stmt.executeUpdate("INSERT INTO accounts (account_id, balance) VALUES (2, 2000)");

// 5. 提交事务
conn.commit();
System.out.println("Transaction committed successfully.");

} catch (SQLException e) {
// 6. 发生异常时回滚事务
try {
if (conn != null) {
conn.rollback();
System.out.println("Transaction rolled back due to error: " + e.getMessage());
}
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// 7. 关闭资源
try {
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

数据源和连接池

数据库连接池(Connection Pool)是个容器,负责分配、管理数据库连接(Connection)。

  • 允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
  • 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。

官方(SUN) 提供的数据库连接池标准接口 DataSource,由第三方组织实现此接口。我们可以通过 DataSource#getConnection 方法来获取 Connection 链接对象。

常见的数据库连接池:

  • DBCP
  • C3P0
  • Druid

MyBatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 支持使用 XML 或注解的方式来配置 SQL 映射,这样可以让业务逻辑和数据库查询相分离,代码更加清晰。

MyBatis 的核心组件

核心配置文件

MyBatis 的核心配置文件(这里我们命名为 mybatis-config.xml)包含了对环境、数据源、别名、映射器等的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<settings>
<!-- 打印sql日志,value 为打印方式 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>

<configuration>
<!-- 环境配置(可用于不同的开发环境,如开发、测试、生产) -->
<environments default="development">
<environment id="development">
<!-- 事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 数据源配置 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>

<!-- 别名配置,简化 Java 类的全路径 -->
<typeAliases>
<typeAlias alias="User" type="com.example.model.User"/>
</typeAliases>

<!-- 映射器配置,指向 Mapper XML 文件的位置 -->
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
</mappers>
</configuration>

数据库映射文件

映射文件(这里我们命名为 Mapper.xml)是用来定义 SQL 语句与 Java 对象之间的关系的,它描述了数据库操作的具体内容。

注意:

  • #{}:用于传递参数,防止 SQL 注入。
  • ${}:用于直接拼接字符串,容易造成 SQL 注入,应谨慎使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 绑定到 Java 接口 -->
<mapper namespace="com.example.mapper.UserMapper">

<!-- 查询用户 -->
<select id="getUserById" parameterType="int" resultType="User">
SELECT id, name, age
FROM users
WHERE id = #{id}
</select>

<!-- 插入用户 -->
<insert id="insertUser" parameterType="User">
INSERT INTO users (name, age) VALUES (#{name}, #{age})
</insert>

<!-- 更新用户 -->
<update id="updateUser" parameterType="User">
UPDATE users
SET name = #{name}, age = #{age}
WHERE id = #{id}
</update>

<!-- 删除用户 -->
<delete id="deleteUserById" parameterType="int">
DELETE FROM users WHERE id = #{id}
</delete>

</mapper>

Java 接口(Mapper 接口)

MyBatis 通过 Java 接口来进行映射,这样可以方便调用数据操作的方法,而不用直接操作 SQL。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.Select;

public interface UserMapper {
// 使用 XML 配置
User getUserById(int id);

// 使用注解配置
@Select("SELECT id, name, age FROM users WHERE id = #{id}")
User getUserByIdUsingAnnotation(int id);
}

这个示例只提供查询用户方法,并实现了两种配置 SQL 映射的方式:

  • getUserById 使用 XML 方式配置 SQL 映射,Mapper.xml 中的 <select > 标签中的属性 id="getUserById" 对应到这个方法,该标签提供了 SQL 语句的相关信息。
  • getUserByIdUsingAnnotation 使用 注解方式配置 SQL 映射,注解中存储 SQL 语句相关信息。

Java 实体类(POJO)

实体类用于将数据库表中的记录映射到 Java 对象上。一个实体类通常是一个普通的 Java 类(POJO),用于保存数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.example.model;

public class User {
private int id;
private String name;
private int age;

// Getter 和 Setter 方法
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;
}
}

MyBatis 的工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import com.example.mapper.UserMapper;
import com.example.model.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;

public class MyBatisExample {
public static void main(String[] args) {
// MyBatis 加载核心配置文件(mybatis-config.xml),初始化环境和数据源。
String resource = "mybatis-config.xml";
try (Reader reader = Resources.getResourceAsReader(resource)) {
// 从配置文件中读取信息,创建 SqlSessionFactory 对象。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 通过 `SqlSessionFactory` 创建 `SqlSession`,用于执行 SQL 操作。
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
// 调用映射器接口的方法执行相应的 SQL 语句,得到查询结果。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1); // userMapper.getUserByIdUsingAnnotation(1);
System.out.println(user.getName());

// 提交事务
sqlSession.commit();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

数据库字段名与 POJO 属性名不一致

在开发 MyBatis 项目的过程中,经常会遇到 数据库表的字段名和 POJO 类的属性名不一致 的情况。比如,数据库表中的列名通常遵循 snake_case(例如 user_name),而 Java 的 POJO 类中的属性名通常遵循 camelCase(例如 userName)。为了映射数据库表中的列与 Java 类中的属性,MyBatis 提供了多种解决方案。

通过 SQL 别名来解决

最直接的方法是在 SQL 查询中使用别名,将数据库列名的结果映射为 POJO 中对应的字段名。

1
2
3
4
5
<select id="getUserById" parameterType="int" resultType="User">
SELECT user_id AS userId, user_name AS userName, user_age AS userAge
FROM users
WHERE user_id = #{id}
</select>

使用 resultMap 进行字段映射

MyBatis 提供了 resultMap 机制,能够将数据库表中的列映射到 POJO 类的属性上,解决字段名和属性名不一致的问题。

这里 <id> 指定主键映射,<result> 指定普通字段的映射。

1
2
3
4
5
6
7
8
9
<resultMap id="userResultMap" type="User">
<id property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="userAge" column="user_age"/>
</resultMap>

<select id="getUserById" parameterType="int" resultMap="userResultMap">
SELECT * FROM users WHERE user_id = #{id}
</select>

使用 @Results 和 @Result 注解

如果希望将配置全部放到 Java 代码中,可以使用 MyBatis 的 @Results 注解,在接口中通过注解配置字段映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

public interface UserMapper {

@Select("SELECT user_id, user_name, user_age FROM users WHERE user_id = #{id}")
@Results({
@Result(property = "userId", column = "user_id"),
@Result(property = "userName", column = "user_name"),
@Result(property = "userAge", column = "user_age")
})
User getUserById(int id);
}

启用驼峰命名转换

可以通过在 MyBatis 配置文件中启用驼峰命名规则,这样 MyBatis 会自动将数据库列名 snake_case 格式转换为 Java 属性的 camelCase 格式。

1
2
3
4
5
6
<configuration>
<settings>
<!-- 启用驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>

MyBatis 的动态 SQL

MyBatis 提供了一些 XML 标签,可以用来构建动态 SQL。常用的标签有:

  • **<if>**:条件判断,用于判断是否包含某些 SQL 片段。
  • **<choose><when><otherwise>**:类似于 Java 的 switch-case,用来实现多分支逻辑。
  • **<trim><where><set>**:用于拼接 SQL,消除多余的逗号或连接符。
  • **<foreach>**:用于在 SQL 中实现循环操作,常用于批量更新或插入。

<if>、<where> 标签

<if> 标签用于根据条件判断是否生成某部分 SQL。常用于有条件拼接 WHERE 子句的场景。<if> 标签使用 test 属性来判断条件是否成立,只有在条件成立时,内部的 SQL 才会生成。

<where> 标签用于自动拼接 WHERE 子句,它会自动处理第一个条件前面的 ANDOR,防止 SQL 语句出错。如果没有条件,<where> 标签不会生成 WHERE 关键字;如果有多个条件,会自动在第一个条件前补充 WHERE,并处理多余的 AND

1
2
3
4
5
6
7
8
9
10
11
<select id="findUsersByConditions" parameterType="map" resultType="User">
SELECT id, name, age FROM users
<where>
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>

<choose>、<when>、<otherwise> 标签

<choose> 标签类似于 Java 中的 switch-case,可以用来实现多分支逻辑判断,在多条件之间选择执行某一个条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="findUsersByChoose" parameterType="map" resultType="User">
SELECT id, name, age FROM users
<where>
<choose>
<when test="name != null">
AND name = #{name}
</when>
<when test="age != null">
AND age = #{age}
</when>
<otherwise>
AND status = 'ACTIVE'
</otherwise>
</choose>
</where>
</select>
  • <when> 标签表示具体的条件,如果 name 不为 null,就生成 AND name = #{name},否则检查下一个条件。
  • <otherwise> 标签类似于 default,当所有 <when> 条件都不满足时,会执行 <otherwise> 中的内容。

<set> 标签

<set> 标签用于构建动态 UPDATE 语句中的 SET 子句,它会自动处理字段之间的逗号,使生成的 SQL 语句合法。<set> 标签会自动去除最后一个字段后面的逗号,确保生成的 SQL 语法正确。

1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateUser" parameterType="User">
UPDATE users
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
</set>
WHERE id = #{id}
</update>

<foreach> 标签

<foreach> 标签用于遍历集合,用于生成包含多个值的 SQL 语句,通常用于 IN 子句或批量插入。

1
2
3
4
5
6
7
<select id="findUsersByIds" parameterType="list" resultType="User">
SELECT id, name, age FROM users
WHERE id IN
<foreach item="id" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</select>
  • collection 属性:指定要遍历的集合名称,例如 list
  • item 属性:表示每次遍历的元素,可以在 SQL 中使用。
  • **openseparatorclose**:分别表示 IN 子句的开始符号 (、分隔符 ,、结束符号 )

<trim> 标签

<trim> 标签用于去除多余的字符,适用于需要灵活去除或者添加字符的情况,例如去掉 SQL 子句前后的 ANDOR、逗号等。

1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateUserWithTrim" parameterType="User">
UPDATE users
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
</trim>
WHERE id = #{id}
</update>
  • prefix 属性:指定前缀,比如 SET,表示生成 SQL 的起始部分。
  • suffixOverrides 属性:表示去除最后的符号,这里是逗号 ,,用于保证 SQL 语句的正确性。

Servlet

Servlet 是一种服务器端 Java 程序,用于处理客户端请求并生成动态响应。Servlet 由 Web 容器(如 Apache Tomcat、Jetty 等)管理,具有高效、可扩展、可移植的特点。

Servlet 是 JavaEE 规范之一,其实就是一个接口,将来我们需要定义 Servlet 类实现 Servlet 接口,并由 web 服务器运行 Servlet。

Servlet 创建

配置 pom.xml

pom.xml 文件中添加必要的依赖项和插件配置。

1
2
3
4
5
6
7
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>

这里 scope 选择 provided 是因为如果将 Servlet API 包含在最终的 WAR 文件中(默认 compile 范围),则会导致该依赖项被打包到 WAR 文件中,可能与 Web 服务器中已有的 Servlet API 版本冲突。使用 provided 范围可以避免这种情况,因为 Web 服务器会提供运行时所需的 Servlet API。

注意:Tomcat 9.x 支持 Servlet 4.0 及以下版本,这些版本的 Servlet API 使用 javax.servlet 包名。而 Tomcat 10.x 引入了对 Servlet 5.0 的支持,Servlet 5.0 开始,所有 API 都从 javax.servlet 迁移到 jakarta.servlet 命名空间。

1
2
3
4
5
6
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>

编写 Servlet 代码

编写一个简单的 Servlet 测试代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;

@WebServlet("/hello")
public class HelloWorldServlet implements Servlet {

public void service(ServletRequest servletRequest, ServletResponse servletResponse) {
System.out.println("Hello,Servlet!");
}

public void init(ServletConfig servletConfig) {
}

public ServletConfig getServletConfig() {
return null;
}

public String getServletInfo() {
return null;
}

public void destroy() {
}
}

访问 Servlet

假设项目名为 my-web-app,则可以通过如下 URL 访问 Servlet 。

1
http://localhost:8080/my-web-app/hello

访问 URL 后,HelloWorldServletservice 方法被调用,在命令行输出信息。

Servlet 基本概念

Servlet 生命周期

Servlet 的生命周期由以下几个阶段组成:

  1. 加载和实例化:Web 容器加载 Servlet 类,并创建 Servlet 实例。这通常在 Servlet 第一次被请求时发生,或者在服务器启动时根据配置预先加载。不过可以通过设置 @WebServlet 注解的 loadOnStartup 来提前创建 Servlet 对象。

    1
    @WebServlet(urlPatterns = "/demo", loadOnStartup = 1)
    • 负整数:第一次被访问时创建 Servlet 对象(默认为 -1)。
    • 0 或正整数:服务器启动时创建 Servlet 对象,数字越小优先级越高。
  2. 初始化:在 Servlet 实例化之后,容器调用 Servlet 的 init() 方法进行初始化。可以在这个方法中执行一次性的初始化任务,如读取配置文件、初始化资源等。该方法只调用一次。

  3. 请求处理:每次有请求到达时,容器会调用 Servlet 的 service() 方法来处理请求。

  4. 销毁:当服务器关闭或者 Servlet 被移除时,容器会调用 Servlet 的 destroy() 方法进行清理工作,如关闭资源、写入日志等。在 destroy() 方法调用之后,容器会释放这个 Servlet 实例,该实例随后会被 Java 的垃圾收集器所回收。

Servlet 体系结构

Servlet 体系结构是基于一组接口和类构建的,主要包括 Servlet 接口、GenericServlet 抽象类和 HttpServlet 类。

  • Servlet:Servlet 体系根接口
  • GenericServlet:Servlet 抽象实现类
  • HttpServlet:对 HTTP 协议封装的 Servlet 实现类

B/S 架构的 web 项目,都是针对 HTTP 协议,所以自定义 Servlet,会继承 HttpServlet

Servlet 路径配置

urlPattern(URL模式)是 Java Servlet 规范中的一个概念,用于将特定的 URL 请求映射到对应的 Servlet。在 Web 应用程序中,urlPattern 定义了哪些 URL 路径应该由哪个 Servlet 处理。这通常在 web.xml 文件中配置,或者通过 Servlet 类的 @WebServlet 注解进行定义。

配置方式

@WebServlet 注解

Servlet 从3.0 版本开始支持使用注解配置,可以通过在 Servlet 类上使用 @WebServlet 注解来定义 URL 模式。

1
2
3
4
5
6
7
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;

@WebServlet(urlPatterns = {"/example", "/example/*", "*.do"})
public class ExampleServlet extends HttpServlet {
// Servlet代码
}

该示例中的 ExampleServlet 会处理以下URL请求:

  • http://localhost:8080/contextPath/example
  • http://localhost:8080/contextPath/example/foo
  • http://localhost:8080/contextPath/action.do

web.xml 配置

Servlet 3.0 版本前只支持 XML 配置文件的配置方法,这需要我们在 web.xml 中配置 Servlet 的访问路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

<!-- 定义 Servlet -->
<servlet>
<servlet-name>ExampleServlet</servlet-name>
<servlet-class>com.example.ExampleServlet</servlet-class>
</servlet>

<!-- 映射 Servlet 到特定的 URL 路径 -->
<servlet-mapping>
<servlet-name>ExampleServlet</servlet-name>
<url-pattern>/example</url-pattern>
</servlet-mapping>

</web-app>
  • <servlet> 元素
    • **<servlet-name>**:指定 Servlet 的名称,用于唯一标识该 Servlet。这是一个逻辑名称,可以是任意字符串。
    • **<servlet-class>**:指定实现 Servlet 的 Java 类的全限定名。该类必须继承自 javax.servlet.http.HttpServlet(或 jakarta.servlet.http.HttpServlet,取决于使用的 Java EE 或 Jakarta EE 版本)。
  • <servlet-mapping>元素
    • **<servlet-name>**:引用前面定义的 <servlet-name>,用于将 Servlet 映射到特定的 URL 路径。
    • **<url-pattern>**:指定该 Servlet 的 URL 访问路径。所有匹配此路径的请求都会由这个 Servlet 处理。

匹配方式

精确匹配

精确匹配指定了一个特定的 URL 路径。只有当请求的 URL 完全匹配这个路径时, Servlet 才会处理该请求。

  • 示例

    1
    2
    3
    4
    <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/myservlet</url-pattern>
    </servlet-mapping>

    处理的URL:http://localhost:8080/contextPath/myservlet

路径匹配(前缀匹配)

路径匹配使用通配符 * 来表示路径前缀匹配。urlPattern 以特定的路径开始,然后是任意的附加路径部分。

  • 示例

    1
    2
    3
    4
    <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/myapp/*</url-pattern>
    </servlet-mapping>

    处理的URL:

    • http://localhost:8080/contextPath/myapp/foo
    • http://localhost:8080/contextPath/myapp/bar

扩展名匹配(后缀匹配)

扩展名匹配使用通配符 * 来表示扩展名匹配。urlPattern 可以匹配特定的文件类型。

  • 示例

    1
    2
    3
    4
    <servlet-mapping>
    <servlet-name>ImageServlet</servlet-name>
    <url-pattern>*.jpg</url-pattern>
    </servlet-mapping>

    处理的URL:

    • http://localhost:8080/contextPath/image1.jpg
    • http://localhost:8080/contextPath/photos/image2.jpg

默认匹配

默认匹配是以 / 作为 urlPattern,它将处理所有未被其他模式匹配的请求。这通常用来作为一个兜底的匹配。

  • 示例

    1
    2
    3
    4
    <servlet-mapping>
    <servlet-name>DefaultServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>

    处理的URL:所有没有被其他模式处理的请求。

Servlet 中的域对象

PageContext(页面范围)

PageContext 对象是专门为 JSP 页面设计的,不能在 Servlet 中直接获取或使用。

HttpServletRequest(请求范围)

HttpServletRequest 对象在每个请求到达 Servlet 时由 Servlet 容器自动传递给 doGet()doPost() 等方法。因此可以使用 request 对象来访问请求范围的数据。

1
2
3
4
5
// 设置请求范围的属性
request.setAttribute("requestMessage", "This is a request scope message");

// 获取请求范围的属性
String message = (String) request.getAttribute("requestMessage");

HttpSession(会话范围)

HttpSession 对象可以通过 HttpServletRequest 对象的 getSession() 方法获取。如果会话不存在,getSession() 方法会自动创建一个新的会话。

1
2
3
4
5
6
7
8
// 获取当前会话,如果没有会话则创建一个新的会话
HttpSession session = request.getSession();

// 设置会话范围的属性
session.setAttribute("sessionMessage", "This is a session scope message");

// 获取会话范围的属性
String sessionMessage = (String) session.getAttribute("sessionMessage");

特有方法

  • **invalidate()**:使会话无效并销毁所有会话数据。

ServletContext(应用范围)

ServletContext 对象表示整个 Web 应用的上下文。它可以通过 HttpServletRequest 对象的 getServletContext() 方法或直接通过 HttpServlet 类的 getServletContext() 方法获取。

1
2
3
4
5
6
7
8
// 获取ServletContext对象
ServletContext context = getServletContext();

// 设置应用范围的属性
context.setAttribute("appMessage", "This is an application scope message");

// 获取应用范围的属性
String appMessage = (String) context.getAttribute("appMessage");

特有方法

  • **getInitParameter(String name)**:获取应用的初始化参数(在 web.xml 中配置)。

请求响应对象

Web 服务器收到客户端的 http 请求,会针对每一次请求,分别创建一个用于代表请求的 request 对象、和代表响应的 response 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/requestInfo/*")
public class RequestInfoServlet extends HttpServlet {

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 处理请求参数的编码 post 请求处理
request.setCharacterEncoding("utf-8");
doGet(request, response);
}

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

request 对象

HttpServletRequest 对象代表客户端的请求,当客户端通过 HTTP 协议访问服务器时,HTTP 请求头中的所有信息都封装在这个对象中。

request 对象获取客户端的请求数据,接口 ServletRequest,子接口 HttpServletRequest 继承 ServletRequestHttpServletRequest 接口的实现类是 Tomcat 引擎提供.

获取请求行信息

假设 Web 应用的上下文路径为 /contextPath,当访问 http://localhost:8080/contextPath/requestInfo/some/path?name=John&age=30 时:

  • request.getMethod():返回请求的 HTTP 方法(如 GETPOST)。
  • request.getRequestURI():返回请求的 URI 部分,不包含查询字符串和协议头
  • request.getRequestURL():返回完整的请求 URL,包括协议(http/https)、服务器名称、端口号和请求路径。
  • request.getRemoteAddr():返回用户的 IP。
  • request.getQueryString():返回 URL 中的查询字符串部分,如果没有查询字符串,则返回 null
  • request.getProtocol():返回 HTTP 协议版本,例如 HTTP/1.1
  • request.getContextPath():返回 Web 应用程序的上下文路径。
  • request.getServletPath():返回与 Servlet 映射的路径部分,即去除上下文路径后的部分。
  • request.getPathInfo():返回请求路径中 Servlet 路径之后的部分,通常用于 RESTful 路径参数解析。

获取请求头信息

HTTP 请求头包含了客户端浏览器发送给服务器的额外信息,如用户代理、接受的内容类型、Cookie 等。这些信息以键值对的形式表示。

我们可以使用 getHeader 函数获取请求头信息,其中 name 为键的字符串形式。

1
public String getHeader(String name)

另外获取请求头信息还有 request.getHeaders(String name)request.getHeaderNames() 等方法,这里不做介绍。

获取请求参数

在 Java Servlet 中,HttpServletRequest 对象提供了多种方法来获取请求参数。请求参数通常是通过 URL 的查询字符串或通过表单提交的数据传递给服务器的。

  • getParameter(String name):返回请求中指定名称的单个参数值。如果参数不存在,返回 null

  • getParameterValues(String name):返回请求中指定名称的所有参数值,作为一个字符串数组。如果参数不存在,返回null。通常用于处理同一个名称有多个值的情况,例如复选框或多选列表。

  • getParameterMap():返回一个包含所有参数名和参数值的 Map<String, String[]>。参数名作为键,参数值作为字符串数组。如果一个参数有多个值,它们都包含在数组中。

    1
    2
    3
    4
    5
    6
    Map<String, String[]> parameterMap = request.getParameterMap();
    for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
    String paramName = entry.getKey();
    String[] paramValues = entry.getValue();
    // 处理参数名及其对应的值数组
    }

reponse 对象

response 对象负责把服务器对请求的处理结果告知客户端。在 B/S 架构中,响应就是把结果带回浏览器。

Servlet 中使用的 ServletResponse 接口,而我们使用的是 ServletResponse 的子接口 HttpServletResponse,它继承自 ServletResponse,是与 HTTP 协议相关的 Response 响应对象。

我们使用的子接口 HttpServletResponse,此接口对象由 Tomcat 引擎提供。

相关方法

  • 设置响应行:使用 HttpServletResponsesetStatus(int sc) 方法可以设置 HTTP 响应的状态码。

  • 设置响应头:响应头信息以键值对的形式表示,主要通过 setHeader 方法设置。

  • 设置响应体

    • response.getWriter():返回一个 PrintWriter 对象,用于向客户端输出字符数据(如文本、HTML、JSON 等)。

      1
      2
      3
      4
      5
      response.setContentType("text/html;charset=UTF-8");
      PrintWriter out = response.getWriter();
      out.println("<html><body>");
      out.println("<h2>Welcome to the Servlet!</h2>");
      out.println("</body></html>");
    • response.getOutputStream():返回一个 ServletOutputStream 对象,用于向客户端输出二进制数据(如图像、PDF、Excel 文件等)。

      1
      2
      3
      4
      5
      6
      response.setContentType("application/pdf");
      ServletOutputStream out = response.getOutputStream();
      // 示例:假设我们已经有一个字节数组代表PDF内容
      byte[] pdfContent = ...;
      out.write(pdfContent);
      out.flush();

重定向

HTTP 重定向是一种常见的 Web 机制,用于告诉客户端(如浏览器)应该请求另一个 URL。当服务器响应一个请求并决定进行重定向时,会发送一个包含重定向指令的响应,客户端接收到这个指令后,会自动发起新的请求到指定的 URL。

我们可以通过手动设置响应字段来实现重定向。这种方式主要涉及设置 HTTP 状态码和 Location 头字段。

  • 设置状态码为 302:302 状态码表示“Found”,是 HTTP 重定向的标准状态码。
  • 设置 Location 头字段:Location 头字段指示客户端应该重定向到的 URL。
1
2
3
4
5
// 设置响应状态码为302重定向
response.setStatus(HttpServletResponse.SC_FOUND); // 或者 response.setStatus(302);

// 设置Location头字段为重定向目标URL
response.setHeader("Location", "http://example.com");

另外我们可以通过 HttpServletResponse 对象的 sendRedirect 方法实现重定向。

1
2
// 重定向到一个名为 /thankYou 的 Servlet 或 JSP 页面
response.sendRedirect(request.getContextPath() + "/thankYou");

请求转发

请求转发(Request Forwarding)是 Java Servlet 中的一种机制,用于在服务器内部将请求从一个 Servlet 或 JSP 转发到另一个 Servlet、JSP 或静态资源(如 HTML 文件)。与重定向不同,请求转发不会改变客户端浏览器的 URL,也不会产生新的 HTTP 请求。整个过程在服务器内部完成,对客户端是透明的。

在 Java Servlet 中,可以使用 RequestDispatcher 对象来实现请求转发。RequestDispatcher 对象可以通过 HttpServletRequestgetRequestDispatcher() 方法获取。

  • 获取 RequestDispatcher 对象:通过 HttpServletRequest 对象获取需要转发的目标资源的 RequestDispatcher 对象。
  • 调用 forward 方法:调用 RequestDispatcher 对象的 forward() 方法,将请求转发到目标资源。
1
2
3
4
5
// 获取RequestDispatcher对象
RequestDispatcher dispatcher = request.getRequestDispatcher("/targetForward");

// 转发请求到另一个Servlet
dispatcher.forward(request, response);

在请求转发资源间可以使用 request 对象共享数据:

  • setAttribute(String name, Object value):以键值对形式存储数据到 request 域中。
  • getAttribute(String name):获取指定名称的属性值。
  • removeAttribute(String name):删除键值对。

相较于重定向,请求转发有如下特点:

  • 浏览器地址栏路径不发生变化。
  • 只能转发到当前服务器的内部资源。
  • 一次请求,可以在转发的资源间使用 request 共享数据。

会话跟踪技术

会话跟踪技术概述

会话

用户打开浏览器,访问 web 服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

  • 从浏览器发出请求到服务端响应数据给前端之后,一次会话(在浏览器和服务器之间)就被建立了。
  • 会话被建立后,如果浏览器或服务端都没有被关闭,则会话就会持续建立着。
  • 浏览器和服务器就可以继续使用该会话进行请求发送和响应,上述的整个过程就被称之为会话

会话跟踪

会话跟踪(Session Tracking Techniques)一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

  • 服务器会收到多个请求,这多个请求可能来自多个浏览器,如上图中的 6 个请求来自 3 个浏览器
  • 服务器需要用来识别请求是否来自同一个浏览器
  • 服务器用来识别浏览器的过程,这个过程就是会话跟踪
  • 服务器识别浏览器后就可以在同一个会话中多次请求之间来共享数据

Cookie 是客户端会话技术,将数据保存到客户端,以后每次请求都携带 Cookie 数据进行访问。

  • 发送 Cookie

    • 创建 Cookie 对象,并设置数据。

      1
      Cookie cookie = new Cookie("key", "value");
    • 设置 Cookie 存活时间(如果不设置,默认存活时间为当前会话)。

      1
      cookie.setMaxAge(60*60*24*7);
    • 使用 response 对象发送 Cookie 到客户端。

      1
      response.addCookie(cookie);
  • 获取 Cookie

    • 使用 request 对象获取客户端携带的所有 Cookie。

      1
      Cookie[] cookies = request.getCookies();
    • 遍历数组,获取每一个 Cookie 对象。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // 检查是否有Cookie
      if (cookies != null) {
      for (Cookie cookie : cookies) {
      // 获取每个Cookie的名称和值
      String name = cookie.getName();
      String value = cookie.getValue();

      // 在响应中显示Cookie信息
      response.getWriter().println("<p>Cookie Name: " + name + "</p>");
      response.getWriter().println("<p>Cookie Value: " + value + "</p>");
      response.getWriter().println("<hr>");
      }
      } else {
      response.getWriter().println("<p>No cookies found.</p>");
      }

Cookie 的实现原理是基于 HTTP 协议的,其中涉及到 HTTP 协议中的两个请求头信息:

  • 响应头:set-cookie
  • 请求头:cookie

例如下面这会话包含次请求/响应:

  • 在响应 1 中,当 Tomcat 发现后端要返回的是一个 Cookie 对象之后,Tomcat 就会在响应头中添加一行数据 Set-Cookie:username=sky
  • 浏览器获取到响应结果后,从响应头中就可以获取到 Set-Cookie 对应值 username=sky,并将数据存储在浏览器的内存中。
  • 浏览器发送请求 2 的时候,浏览器会自动在请求头中添加 Cookie: username=sky

Session

Session 是服务端会话跟踪技术,将数据保存到服务端。(可以理解为与 Cookie 效果相同,只不过数据存储在服务端。)

Session 的基本使用

在 JavaEE 中提供了 HttpSession 接口,来实现一次会话的多次请求之间数据共享功能。具体可以参考前面 Servlet 中的域对象的 HttpSession 部分。

Session 的原理分析

由于 Session 和 Cookie 一样也要区分和记录会话,且 HTTP 是无状态的协议,因此 Session 也是基于 Cookie 来实现的,只不过这个过程对用户透明。

  • 在第一次获取 session 对象的时候,session 对象会有一个唯一的标识,假如是 id:10
  • 在 session 中存入其他数据并处理完成所有业务后,需要通过 Tomcat 服务器响应结果给浏览器。
  • Tomcat 服务器发现业务处理中使用了 session 对象,就会把 session 的唯一标识 id:10 当做一个 cookie,添加 Set-Cookie:JESSIONID=10 到响应头中,并响应给浏览器。
  • 浏览器接收到响应结果后,会把响应头中的 coookie 数据存储到浏览器的内存中。
  • 浏览器在同一会话中再次发生请求时,会把 cookie 中的数据按照 cookie: JESSIONID=10 的格式添加到请求头中并发送给服务器 Tomcat 。
  • Servlet 获取到请求后,从请求头中就读取 cookie 中的 JSESSIONID 值为 10,然后就会到服务器内存中寻找 id:10 的 session 对象,如果找到了,就直接返回该对象,如果没有则新创建一个 session 对象。

因此 session 本质就是把数据存储在服务端,而客户端只存数据的句柄,客户端凭句柄找到服务器中对应的数据,并且客户端自始至终都得不到 session 中存储的数据。

Filter

  • Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
  • 过滤器一般完成一些通用的操作,比如:权限控制、统一编码处理、敏感字符处理等等…

Filter 生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/exampleServlet") // 指定拦截的URL模式
public class ExampleFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作(如读取Filter配置参数)
System.out.println("ExampleFilter initialized");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

// 在请求处理之前的操作
System.out.println("Before processing request in ExampleFilter");

// 继续将请求传递给下一个过滤器或目标Servlet
chain.doFilter(request, response);

// 在响应处理之后的操作
System.out.println("After processing request in ExampleFilter");
}

@Override
public void destroy() {
// 释放资源的操作
System.out.println("ExampleFilter destroyed");
}
}

Filter 的生命周期由 Servlet 容器管理,主要包括以下三个阶段:

  1. 初始化(init)
    • 当Servlet容器启动或第一次处理与 Filter 关联的请求时,调用 Filter 的 init() 方法。开发者可以在 init() 方法中执行初始化操作,如读取配置参数。
  2. 过滤(doFilter)
    • 每次请求与 Filter 匹配时,容器会调用 doFilter() 方法。该方法接收三个参数:ServletRequestServletResponseFilterChain
    • 开发者可以在 doFilter() 方法中对请求和响应进行处理,还可以决定是否将请求继续传递给下一个 Filter 或最终的 Servlet。
  3. 销毁(destroy)
    • 当 Servlet 容器销毁 Filter 时,调用 destroy() 方法。开发者可以在此方法中释放资源。

配置 Filter

Filter 可以通过两种方式配置:注解方式和 web.xml 文件配置方式。

1. 使用注解配置

在 Java EE 6 及以上版本,可以使用 @WebFilter 注解直接配置 Filter。

1
2
3
4
@WebFilter(urlPatterns = {"/exampleServlet"})
public class ExampleFilter implements Filter {
// 实现Filter接口的方法
}
  • **urlPatterns**:指定 Filter 拦截的 URL 模式,支持通配符。

2. 在web.xml 中配置

web.xml 文件中手动配置 Filter,并指定拦截的 URL 模式。

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>ExampleFilter</filter-name>
<filter-class>com.example.ExampleFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>ExampleFilter</filter-name>
<url-pattern>/exampleServlet</url-pattern>
</filter-mapping>
  • **<filter>**:定义 Filter 的名称和实现类。
  • **<filter-mapping>**:定义 Filter 与 URL 模式之间的映射关系。

Filter 的常见应用场景

日志记录

Filter 可以用于记录请求的详细信息,如请求的 URL、客户端 IP 地址、请求参数等。

1
2
3
4
5
6
7
8
9
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) request;
System.out.println("Request URI: " + req.getRequestURI());

chain.doFilter(request, response);
}

用户认证与授权

Filter 可以用于检查用户的登录状态或访问权限,如果用户未登录或无权限访问,可以重定向到登录页面或显示错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;

HttpSession session = req.getSession(false);
boolean loggedIn = (session != null && session.getAttribute("user") != null);

if (loggedIn) {
chain.doFilter(request, response);
} else {
res.sendRedirect(req.getContextPath() + "/login.jsp");
}
}

请求和响应的字符编码设置

Filter 可以用于设置请求和响应的字符编码,确保处理过程中不会出现乱码。

1
2
3
4
5
6
7
8
9
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");

chain.doFilter(request, response);
}

响应压缩

Filter 可以用于对响应内容进行压缩,以减少传输数据量,提高页面加载速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

HttpServletResponse httpResponse = (HttpServletResponse) response;

// 设置压缩标头
httpResponse.setHeader("Content-Encoding", "gzip");

// 使用压缩输出流
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(httpResponse.getOutputStream());
HttpServletResponseWrapper wrappedResponse = new HttpServletResponseWrapper(httpResponse) {
public ServletOutputStream getOutputStream() throws IOException {
return new ServletOutputStream() {
public void write(int b) throws IOException {
gzipOutputStream.write(b);
}

public void flush() throws IOException {
gzipOutputStream.flush();
}

public void close() throws IOException {
gzipOutputStream.close();
}
};
}
};

chain.doFilter(request, wrappedResponse);
}

Filter Chain(过滤器链)

当有多个 Filter 配置时,请求会依次通过这些 Filter,这些 Filter 形成一个链条,称为过滤器链(Filter Chain)

每个 Filter 在处理完请求后,可以选择将请求传递给链中的下一个 Filter 或目标 Servlet。如果一个 Filter 没有调用 chain.doFilter() 方法,则请求不会继续传递,而是直接终止在该 Filter 中。

关于 Filter 的执行顺序:

  • 在 web.xml 中配置的 Filter:过滤器在web.xml中声明的顺序(<filter-mapping>)决定了它们在过滤器链中的执行顺序。先声明的过滤器会先执行。
  • 使用 @WebFilter 注解配置的 Filter:如果所有过滤器都使用注解配置,那么它们的执行顺序取决于容器的处理顺序,通常是按照类加载的顺序。某些框架或容器支持通过注解中的 order 属性来指定顺序。

Listener

在Java Web应用中,Listener(监听器)是用于监听和响应特定事件的组件。Listener可以监听Web应用程序、会话、请求的生命周期事件,以及属性的变化。当这些事件发生时,Listener可以执行相应的操作,例如初始化资源、清理资源、记录日志等。

Listener 的类型

Java Web 中的 Listener 主要分为以下几类:

监听器分类 监听器名称 作用
ServletContext 监听 ServletContextListener 用于对 ServletContext 对象进行监听(创建、销毁)
ServletContextAttributeListener 对 ServletContext 对象中属性的监听(增删改属性)
Session 监听 HttpSessionListener 对 Session 对象的整体状态的监听(创建、销毁)
HttpSessionAttributeListener 对 Session 对象中的属性监听(增删改属性)
HttpSessionBindingListener 监听对象于 Session 中的绑定和解除
HttpSessionActivationListener 对 Session 数据的钝化和活化的监听
Request 监听 ServletRequestListener 对 Request 对象进行监听(创建、销毁)
ServletRequestAttributeListener 对 Request 对象中属性的监听(增删改属性)

每一种监听器都会提供一些接口共用户实现来实现特定的监听功能,以 HttpSessionListener 为例,该监听器用于监听用户会话的创建和销毁事件,常用于统计在线用户数、管理会话相关资源等。

接口方法

  • sessionCreated(HttpSessionEvent se):当HttpSession被创建时调用。
  • sessionDestroyed(HttpSessionEvent se):当HttpSession被销毁时调用。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class MyHttpSessionListener implements HttpSessionListener {

@Override
public void sessionCreated(HttpSessionEvent se) {
// 会话创建时的操作
System.out.println("会话创建:" + se.getSession().getId());
}

@Override
public void sessionDestroyed(HttpSessionEvent se) {
// 会话销毁时的操作
System.out.println("会话销毁:" + se.getSession().getId());
}
}

Listener 的配置

使用注解配置

Listener可以通过@WebListener注解来声明,注解配置方式简单、直观。

1
2
3
4
5
6
7
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class MyServletContextListener implements ServletContextListener {
// 实现接口方法
}

在 web.xml 中配置

Listener 也可以在 web.xml 中配置,适用于更传统的配置方式。

1
2
3
<listener>
<listener-class>com.example.MyServletContextListener</listener-class>
</listener>

Listener的使用场景

  • 资源管理:在应用启动时初始化资源(如数据库连接池),在应用关闭时释放资源。
  • 会话管理:监控和管理用户会话,统计在线用户数,记录用户会话日志等。
  • 请求监控:记录请求日志,分析请求流量,监控请求生命周期。
  • 属性监控:监控应用范围、会话范围或请求范围属性的变化,触发相应的业务逻辑。

JSP

JSP(JavaServer Pages)是Java EE(Jakarta EE)技术的一部分,用于创建动态 Web 内容。JSP 页面以 HTML、XML 等标记语言为基础,并嵌入了 Java 代码。JSP 是 Java Servlet 技术的一种扩展,它使得开发者可以更方便地创建动态 Web 页面。

在 Java Web 项目使用 JSP 需要在 pom.xml 文件中添加必要的依赖项和插件配置。

1
2
3
4
5
6
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>

JSP 原理

  • 请求JSP页面:客户端(通常是浏览器)发送一个 HTTP 请求,要求访问某个 JSP 页面。
  • JSP编译:如果 JSP 页面是首次请求或 JSP 页面发生了变化,Web 服务器将其编译为 Java Servlet 类。
  • JSP执行:生成的 Servlet 类由 Servlet 容器执行,Java 代码在服务器端运行,并生成动态内容。
  • 响应返回:生成的动态内容(通常是 HTML)作为 HTTP 响应返回给客户端。

JSP 基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>hello jsp</h1>

<%
System.out.println("hello,jsp~");
%>
</body>
</html>

JSP 脚本元素

  • 脚本片段(Scriptlet):用 <% ... %> 括起来的 Java 代码片段,它们在服务器端执行。

    1
    2
    3
    4
    <%
    String name = "John Doe";
    out.println("Hello, " + name + "!");
    %>
  • 声明(Declaration):用 <%! ... %> 括起来的 Java 声明。这些声明中的变量和方法可以在整个 JSP 页面中使用。

    1
    2
    3
    4
    5
    6
    7
    <%! 
    private int counter = 0;

    public int getCounter() {
    return counter++;
    }
    %>
  • 表达式(Expression):用 <%= ... %> 括起来的 Java 表达式,它们的值会被计算并插入到页面中。

    1
    <h2>The current count is: <%= getCounter() %></h2>

JSP 指令

  • page指令:配置 JSP 页面的全局属性,如 contentTypeimport 等。

    1
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  • include指令:在编译时包含另一个文件的内容。

    1
    <%@ include file="header.jsp" %>

EL 表达式和域对象

Expression Language 表达式语言,用于简化 JSP 页面内的 Java 代码。EL 通常用 ${} 表示,主要用来获取数据。EL 表达式的主要作用就是省略掉 getAttribute 方法。

从四种域对象中获取指定属性的时候可以指定域对象的范围,注意这里的域对象的表示。如果不指定域对象则按照就近原则获取。

1
2
3
4
<p>${pageScope.message}</p>         <!-- 从 pageScope(页面范围)中获取属性 message -->
<p>${requestScope.username}</p> <!-- 从 requestScope(请求范围)中获取属性 username -->
<p>${sessionScope.user}</p> <!-- 从 sessionScope(会话范围)中获取属性 user -->
<p>${applicationScope.config}</p> <!-- 从 applicationScope(应用范围)中获取属性 config -->

其中 pageScope 仅在页面有效,因此属性设置也是在页面中设置。

1
2
3
4
5
<%-- 设置 pageScope 属性 --%>
<c:set var="message" value="This is a page scope message" scope="page"/>

<%-- 访问 pageScope 属性 --%>
<p>${pageScope.message}</p>

JSTL 标签

JSP 标准标签库(Jsp Standarded Tag Library) ,使用标签取代 JSP 页面上的 Java 代码。

要想使用 JSTL 标签,首先要在 pom.xml 中导入依赖。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>

然后在 JSP 页面上引入 JSTL 标签库。

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

JSTL 标签示例代码如下,不做过多介绍。

1
2
3
4
5
6
7
<c:if test="${user != null}">
<p>Welcome, ${user.name}!</p>
</c:if>

<c:forEach var="item" items="${itemList}">
<p>${item}</p>
</c:forEach>

Spring

  • Title: Java Web 开发基础
  • Author: sky123
  • Created at : 2024-11-11 23:54:46
  • Updated at : 2025-08-19 01:03:10
  • Link: https://skyi23.github.io/2024/11/11/Java Web 开发基础/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
Java Web 开发基础