Servlet 技术详解:从入门到实战 🦞

Servlet 是 Java Web 开发的基础,是所有 Java Web 框架(如 Spring MVC)的底层原理。理解和掌握 Servlet 的工作机制,对于每一个 Java 后端开发者来说都是必备技能。本文将带你从零开始,系统掌握 Servlet 的核心概念、工作原理、生命周期以及实战开发。💪


📚 目录导航


一、Servlet 概述:什么是 Servlet?

1.1 Servlet 的定义

Servlet 是 Sun 公司制定的 Java Servlet API 规范,它是一种运行在服务器端的 Java 程序,用于处理客户端(浏览器)的请求并返回响应。

1.2 Servlet 与 JSP 的关系

对比维度 Servlet JSP
本质 Java 类 HTML + Java 代码混合
擅长 业务逻辑、数据处理 页面展示、动态内容
编译 手动编译 首次访问时自动编译成 Servlet
书写 纯 Java 代码 HTML 中嵌入 Java 代码
运行 必须部署到 Servlet 容器 运行在 Servlet 容器中
适用场景 后端接口、数据处理 动态页面、模板渲染

💡 推荐:Servlet 负责后端逻辑,JSP 负责页面展示,分工明确。现代开发更推荐前后端分离,使用 JSON 作为数据交换格式。

1.3 Servlet 容器的作用

常见的 Servlet 容器有:Tomcat、Jetty、Undertow、WebLogic、WebSphere 等。

1.4 Servlet 版本演进

版本 发布年份 主要特性
Servlet 2.0 1997 最早的 Servlet 规范
Servlet 2.3/2.4 2001/2003 过滤器的引入
Servlet 2.5 2005 注解支持增强
Servlet 3.0 2009 注解驱动、异步支持、模块化
Servlet 3.1 2013 非阻塞 I/O
Servlet 4.0 2017 HTTP/2 支持
Servlet 5.0+ 2020+ Jakarta EE 迁移

二、Tomcat 服务器安装与配置

2.1 Tomcat 简介

Apache Tomcat 是 Apache 软件基金会开发的开源 Servlet 容器,是目前最流行的 Java Web 服务器之一。

2.2 Tomcat 安装(Windows)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 下载 Tomcat
# 下载地址:https://tomcat.apache.org/download-10.cgi
# 选择 10.x 版本(对应 Servlet 5.0+)

# 2. 解压到指定目录
# 例如:D:\apache-tomcat-10.1.0

# 3. 配置环境变量(可选)
# CATALINA_HOME = D:\apache-tomcat-10.1.0
# PATH = %CATALINA_HOME%\bin

# 4. 目录结构说明
# bin/ - 启动/关闭脚本
# conf/ - 配置文件
# lib/ - JAR 包
# logs/ - 日志文件
# webapps/ - 部署的应用
# work/ - 临时文件(JSP 编译)

2.3 Tomcat 目录结构

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
apache-tomcat-10.1.0/
├── bin/ # 启动脚本
│ ├── startup.bat # Windows 启动
│ ├── startup.sh # Linux 启动
│ ├── shutdown.bat # Windows 关闭
│ └── version.sh # 查看版本

├── conf/ # 配置文件
│ ├── server.xml # 服务器配置
│ ├── web.xml # Web 应用配置
│ ├── context.xml # 上下文配置
│ └── tomcat-users.xml # 用户认证

├── lib/ # Tomcat 依赖 JAR

├── logs/ # 日志目录
│ ├── catalina.out # 启动日志
│ └── localhost.log # 应用日志

├── webapps/ # Web 应用目录
│ ├── docs/ # Tomcat 文档
│ ├── examples/ # 示例应用
│ └── manager/ # 管理应用

└── work/ # 工作目录
└── Catalina/ # JSP 编译目录

2.4 启动 Tomcat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Windows 启动
# 方式一:双击 bin/startup.bat
# 方式二:命令行
cd D:\apache-tomcat-10.1.0\bin
startup.bat

# Linux 启动
./bin/startup.sh

# 验证启动成功
# 浏览器访问:http://localhost:8080
# 看到 Tomcat 主页说明启动成功

# 关闭 Tomcat
# Windows
shutdown.bat
# Linux
./bin/shutdown.sh

2.5 修改端口号

1
2
3
4
5
6
7
8
9
10
<!-- conf/server.xml -->
<Server port="8005" shutdown="SHUTDOWN">

<!-- 修改 Connector 的 port 属性即可 -->
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
</Service>
</Server>

三、第一个 Servlet 程序

3.1 项目结构设计

3.2 Maven 项目配置

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
50
51
52
53
54
55
56
57
58
59
60
<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<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>

<groupId>com.example</groupId>
<artifactId>servlet-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!-- Servlet API(provided:运行时由容器提供) -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>

<!-- JSP API -->
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

<!-- JSTL 标签库 -->
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>

<build>
<finalName>servlet-demo</finalName>
<plugins>
<!-- Tomcat 插件(方便运行) -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>3.0-r1655215</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>

3.3 创建 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.example.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;

/**
* 第一个 Servlet 程序
* 访问地址:http://localhost:8080/first-servlet
*/
@WebServlet("/first-servlet") // 注解配置 URL 映射
public class FirstServlet extends HttpServlet {

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

// 设置响应内容类型和字符编码
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");

// 获取输出流
PrintWriter out = response.getWriter();

// 输出 HTML 内容
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<meta charset='UTF-8'>");
out.println("<title>第一个 Servlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>🎉 Hello, Servlet! 🦞</h1>");
out.println("<p>当前时间:" + LocalDateTime.now() + "</p>");
out.println("<p>当前会话 ID:" + request.getSession().getId() + "</p>");
out.println("<p>Servlet 版本:jakarta.servlet-api 6.0</p>");
out.println("</body>");
out.println("</html>");
}
}

3.4 传统 XML 配置方式

如果使用 Servlet 3.0 之前的版本,或者项目不使用注解,需要在 web.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
<!-- src/main/webapp/WEB-INF/web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">

<display-name>Servlet Demo Application</display-name>

<!-- 配置 Servlet -->
<servlet>
<!-- Servlet 名称 -->
<servlet-name>HelloServlet</servlet-name>
<!-- Servlet 类的全限定名 -->
<servlet-class>com.example.servlet.HelloServlet</servlet-class>
<!-- 启动顺序(可选) -->
<load-on-startup>1</load-on-startup>
</servlet>

<!-- 配置 Servlet 映射 -->
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<!-- 访问 URL -->
<url-pattern>/hello</url-pattern>
</servlet-mapping>

<!-- 欢迎页面 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

</web-app>

3.5 运行项目

1
2
3
4
5
6
7
8
9
10
11
# 方式一:使用 Maven 插件运行
mvn tomcat:run

# 方式二:手动部署
# 1. 打包 WAR 文件
mvn package

# 2. 将 target/servlet-demo.war 复制到 Tomcat 的 webapps 目录

# 3. 启动 Tomcat
# 访问地址:http://localhost:8080/servlet-demo/first-servlet

四、Servlet 生命周期

4.1 生命周期四个阶段

阶段 执行时机 执行次数 说明
实例化 首次请求时(或容器启动时 1 次 Servlet 容器创建 Servlet 实例
初始化 实例化后 1 次 调用 init() 方法,可获取初始化参数
服务 每次请求 多次 调用 service() → 分发到 doGet/doPost
销毁 应用卸载/容器关闭 1 次 调用 destroy(),释放资源

4.2 生命周期代码演示

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
50
51
52
53
54
package com.example.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;

/**
* Servlet 生命周期演示
*/
@WebServlet("/lifecycle")
public class LifecycleServlet extends HttpServlet {

// 计数器:统计请求次数
private int requestCount = 0;

public LifecycleServlet() {
super();
System.out.println("📦 1. LifecycleServlet 构造方法被调用");
}

@Override
public void init() throws ServletException {
super.init();
System.out.println("🔧 2. init() 方法被调用,Servlet 初始化完成");
}

@Override
protected void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
requestCount++;
System.out.println("🔄 3. service() 方法被调用,第 " + requestCount + " 次请求");
super.service(request, response);
}

@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
System.out.println("📝 4. doGet() 方法被调用");
response.getWriter().write(
"<h1>Servlet 生命周期演示</h1>" +
"<p>请求次数:" + requestCount + "</p>" +
"<p>当前会话 ID:" + request.getSession().getId() + "</p>"
);
}

@Override
public void destroy() {
super.destroy();
System.out.println("🗑️ 5. destroy() 方法被调用,Servlet 即将被销毁");
System.out.println("总共处理了 " + requestCount + " 次请求");
}
}

4.3 init() 方法详解

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
/**
* init() 方法的重写方式
*/

// 方式一:重写无参 init()
@Override
public void init() throws ServletException {
// 获取初始化参数(web.xml 中配置)
String encoding = getServletConfig().getInitParameter("encoding");
System.out.println("编码格式:" + encoding);
}

// 方式二:重写带 ServletConfig 的 init()
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// 可以在调用 super.init(config) 之后使用 config
}

// 方式三:使用 @PostConstruct 注解(替代 init)
import jakarta.annotation.PostConstruct;

@PostConstruct
public void initMethod() {
System.out.println("初始化方法被调用");
}

五、HttpServletRequest 与 HttpServletResponse

5.1 HttpServletRequest 对象

HttpServletRequest 封装了客户端的 HTTP 请求信息:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* HttpServletRequest 常用方法
*/
@WebServlet("/request-demo")
public class RequestDemoServlet extends HttpServlet {

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

// ========== 获取请求信息 ==========

// 获取请求方法
String method = request.getMethod(); // GET / POST

// 获取请求 URL
StringBuffer url = request.getRequestURL(); // http://localhost:8080/demo
String uri = request.getRequestURI(); // /demo

// 获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");

// 获取多个值(如 checkbox)
String[] hobbies = request.getParameterValues("hobby");

// 获取请求头
String userAgent = request.getHeader("User-Agent");
String contentType = request.getContentType();
int contentLength = request.getContentLength();

// 获取 Session
javax.servlet.http.HttpSession session = request.getSession();

// 获取 Cookie
Cookie[] cookies = request.getCookies();

// 获取请求体(POST 请求体)
// 需要先设置编码,否则中文乱码
request.setCharacterEncoding("UTF-8");
BufferedReader reader = request.getReader();
String line;
StringBuilder body = new StringBuilder();
while ((line = reader.readLine()) != null) {
body.append(line);
}

// ========== 存储请求域属性 ==========
// 用于在同一次请求中传递数据
request.setAttribute("user", new User("张三", 25));

// 获取请求域属性
User user = (User) request.getAttribute("user");

// 获取转发对象
RequestDispatcher dispatcher = request.getRequestDispatcher("/other");
// dispatcher.forward(request, response); // 转发
// dispatcher.include(request, response); // 包含

response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("请求方法:" + method + "<br/>");
response.getWriter().write("请求 URL:" + url);
}
}

5.2 HttpServletResponse 对象

HttpServletResponse 用于构建 HTTP 响应:

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
50
51
/**
* HttpServletResponse 常用方法
*/
@WebServlet("/response-demo")
public class ResponseDemoServlet extends HttpServlet {

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

// ========== 设置响应 ==========

// 设置响应内容类型
response.setContentType("text/html;charset=UTF-8");
// 其他常用类型:
// application/json;charset=UTF-8
// text/plain;charset=UTF-8
// image/png

// 设置响应状态码
response.setStatus(200); // 正常
// response.setStatus(404); // 未找到
// response.setStatus(500); // 服务器内部错误

// 设置响应头
response.setHeader("Cache-Control", "no-cache");
response.setHeader("X-Custom-Header", "custom-value");

// ========== 获取输出流 ==========

// 字符流(适合文本内容)
PrintWriter writer = response.getWriter();
writer.write("Hello, World!");

// 字节流(适合二进制内容,如图片)
// ServletOutputStream out = response.getOutputStream();
// out.write(imageBytes);

// ========== 重定向 ==========
// 浏览器请求 /redirect-demo,服务器告诉浏览器跳转到 /target
response.sendRedirect(request.getContextPath() + "/target");

// ========== 设置 Cookie ==========
Cookie cookie = new Cookie("username", "zhangsan");
cookie.setMaxAge(3600); // 有效期(秒)
cookie.setPath("/"); // 有效路径
cookie.setHttpOnly(true); // 禁止 JavaScript 访问
response.addCookie(cookie);
}
}

5.3 请求参数中文乱码解决

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
/**
* 中文乱码问题解决方案
*/
@WebServlet("/encoding-demo")
public class EncodingDemoServlet extends HttpServlet {

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

// GET 请求参数乱码:在 Tomcat server.xml 中配置
// <Connector port="8080" URIEncoding="UTF-8" />

// 或者使用 URLEncoder/URLDecoder 编解码
String name = request.getParameter("name");
name = new String(name.getBytes("ISO-8859-1"), "UTF-8");

response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("GET 参数:" + name);
}

@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

// POST 请求参数乱码:设置请求编码
request.setCharacterEncoding("UTF-8");

String username = request.getParameter("username");
String password = request.getParameter("password");

response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("POST 参数:username=" + username + ", password=" + password);
}
}

6.1 会话管理概念

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
/**
* Cookie 操作示例
*/
@WebServlet("/cookie-demo")
public class CookieDemoServlet extends HttpServlet {

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

// ========== 创建 Cookie ==========
Cookie usernameCookie = new Cookie("username", "zhangsan");
usernameCookie.setMaxAge(3600); // 有效期 1 小时
usernameCookie.setPath("/"); // 整个应用有效
usernameCookie.setHttpOnly(true); // 防止 XSS 攻击
response.addCookie(usernameCookie);

// ========== 读取 Cookie ==========
Cookie[] cookies = request.getCookies();
String username = null;
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("username".equals(cookie.getName())) {
username = cookie.getValue();
break;
}
}
}

// ========== 删除 Cookie ==========
Cookie deleteCookie = new Cookie("username", "");
deleteCookie.setMaxAge(0); // 设置为 0 表示删除
deleteCookie.setPath("/");
response.addCookie(deleteCookie);

response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(
"<h1>Cookie 演示 �饼干</h1>" +
"<p>用户名:" + (username != null ? username : "未设置") + "</p>"
);
}
}

6.3 Session 使用示例

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
50
/**
* HttpSession 操作示例
*/
@WebServlet("/session-demo")
public class SessionDemoServlet extends HttpServlet {

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

// ========== 获取 Session ==========
// request.getSession() 等价于 request.getSession(true)
// true:如果不存在则创建新的 Session
// false:如果不存在则返回 null
javax.servlet.http.HttpSession session = request.getSession();

// ========== 存储数据 ==========
session.setAttribute("userId", 1001L);
session.setAttribute("username", "zhangsan");

// ========== 读取数据 ==========
Long userId = (Long) session.getAttribute("userId");
String username = (String) session.getAttribute("username");

// ========== 移除数据 ==========
session.removeAttribute("userId");

// ========== Session 配置 ==========
session.setMaxInactiveInterval(1800); // 30 分钟无活动则过期
session.getMaxInactiveInterval(); // 获取超时时间

// ========== 使 Session 失效 ==========
// session.invalidate(); // 销毁整个 Session

// ========== 获取 Session 信息 ==========
String sessionId = session.getId(); // Session ID
long creationTime = session.getCreationTime(); // 创建时间
long lastAccessedTime = session.getLastAccessedTime(); // 最后访问时间
boolean isNew = session.isNew(); // 是否新建

response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(
"<h1>Session 演示 🗃️</h1>" +
"<p>Session ID:" + sessionId + "</p>" +
"<p>用户名:" + username + "</p>" +
"<p>是否新 Session:" + isNew + "</p>"
);
}
}

6.4 Session 失效的几种情况

对比维度 Cookie Session
存储位置 浏览器本地 服务器内存
存储容量 每个 Cookie 最大 4KB 理论上无限制
安全性 较低(可被禁用/伪造) 较高(数据在服务器端)
性能 不占用服务器资源 占用服务器内存
跨会话 可设置跨会话持久化 仅当前会话有效
适用场景 自动登录、记住我 登录状态、购物车

七、过滤器与监听器

7.1 过滤器(Filter)概述

过滤器是 Servlet 2.3 规范引入的组件,用于在请求到达 Servlet 之前或响应返回之前对请求/响应进行预处理。

7.2 过滤器示例

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
package com.example.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* 字符编码过滤器
*/
@WebFilter("/*") // 拦截所有请求
public class EncodingFilter implements Filter {

private String encoding = "UTF-8";

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 获取初始化参数
String initEncoding = filterConfig.getInitParameter("encoding");
if (initEncoding != null && !initEncoding.isEmpty()) {
encoding = initEncoding;
}
System.out.println("✅ EncodingFilter 初始化完成,编码:" + encoding);
}

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

// 请求预处理
request.setCharacterEncoding(encoding);
response.setCharacterEncoding(encoding);
response.setContentType("text/html;charset=" + encoding);

System.out.println("🔍 请求被 EncodingFilter 拦截");

// 放行请求
chain.doFilter(request, response);

// 响应后处理
System.out.println("🔍 响应被 EncodingFilter 处理完成");
}

@Override
public void destroy() {
System.out.println("🗑️ EncodingFilter 被销毁");
}
}

7.3 用户登录过滤器

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
/**
* 登录验证过滤器
*/
@WebFilter(urlPatterns = {"/admin/*", "/api/*"})
public class LoginFilter implements Filter {

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

HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

// 获取 Session 中的用户信息
javax.servlet.http.HttpSession session = httpRequest.getSession(false);
Object user = session != null ? session.getAttribute("user") : null;

if (user != null) {
// 已登录,放行
chain.doFilter(request, response);
} else {
// 未登录,重定向到登录页
httpResponse.sendRedirect(httpRequest.getContextPath() + "/login");
}
}
}

7.4 监听器(Listener)概述

监听器类型 接口 触发时机
ServletContext 监听器 ServletContextListener 应用启动/停止
Session 监听器 HttpSessionListener Session 创建/销毁
请求监听器 ServletRequestListener 请求创建/销毁
属性监听器 ServletContextAttributeListener 等 属性添加/删除/替换

7.5 监听器示例

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
50
51
52
53
54
55
package com.example.listener;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebListener;

/**
* 应用启动监听器
*/
@WebListener
public class ApplicationListener implements ServletContextListener {

@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("🚀 应用启动:Spring Boot 初始化中...");

// 可以在这里加载配置文件、初始化数据库连接池等
ServletContext context = sce.getServletContext();
context.setAttribute("appName", "Servlet Demo");

// 获取初始化参数
String adminEmail = context.getInitParameter("adminEmail");
System.out.println("管理员邮箱:" + adminEmail);
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("🛑 应用停止:资源释放中...");
}
}

/**
* Session 监听器:统计在线人数
*/
@WebListener
public class SessionListener implements HttpSessionListener {

// 存储在线人数(需要线程安全)
private static int onlineCount = 0;

@Override
public void sessionCreated(HttpSessionEvent se) {
onlineCount++;
System.out.println("👤 Session 创建,当前在线人数:" + onlineCount);
}

@Override
public void sessionDestroyed(HttpSessionEvent se) {
onlineCount--;
System.out.println("👋 Session 销毁,当前在线人数:" + onlineCount);
}

public static int getOnlineCount() {
return onlineCount;
}
}

八、Servlet 注解开发

8.1 常用注解一览

注解 作用 示例
@WebServlet 配置 Servlet 的 URL 映射 @WebServlet("/user")
@WebFilter 配置过滤器 @WebFilter("/*")
@WebListener 配置监听器 @WebListener
@WebInitParam 配置初始化参数 @WebInitParam(name="encoding", value="UTF-8")
@MultipartConfig 配置文件上传 @MultipartConfig

8.2 完整注解示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package com.example.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

/**
* 完整的注解配置示例
*/
@WebServlet(
name = "UserServlet", // Servlet 名称
urlPatterns = {"/api/user", "/api/users"}, // 多个 URL 映射
initParams = { // 初始化参数
@WebInitParam(name = "encoding", value = "UTF-8"),
@WebInitParam(name = "pageSize", value = "20")
},
loadOnStartup = 1, // 启动时加载
description = "用户管理 Servlet" // 描述
)
@MultipartConfig( // 文件上传配置
fileSizeThreshold = 1024 * 1024, // 超过 1MB 则写入磁盘
maxFileSize = 10 * 1024 * 1024, // 最大文件大小 10MB
maxRequestSize = 50 * 1024 * 1024 // 最大请求大小 50MB
)
public class UserServlet extends HttpServlet {

private String encoding;
private int pageSize;

@Override
public void init() throws ServletException {
// 获取初始化参数
encoding = getServletConfig().getInitParameter("encoding");
pageSize = Integer.parseInt(getServletConfig().getInitParameter("pageSize"));
System.out.println("✅ UserServlet 初始化:encoding=" + encoding + ", pageSize=" + pageSize);
}

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

String action = request.getParameter("action");

if ("list".equals(action)) {
listUsers(request, response);
} else if ("detail".equals(action)) {
getUserDetail(request, response);
} else {
listUsers(request, response);
}
}

@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

request.setCharacterEncoding(encoding);

String action = request.getParameter("action");

if ("create".equals(action)) {
createUser(request, response);
} else if ("update".equals(action)) {
updateUser(request, response);
} else if ("delete".equals(action)) {
deleteUser(request, response);
} else if ("upload".equals(action)) {
uploadAvatar(request, response);
}
}

private void listUsers(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json;charset=" + encoding);
PrintWriter out = response.getWriter();
out.println("{\"users\":[{\"id\":1,\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}]}");
}

private void getUserDetail(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String id = request.getParameter("id");
response.setContentType("application/json;charset=" + encoding);
PrintWriter out = response.getWriter();
out.println("{\"id\":" + id + ",\"name\":\"张三\",\"email\":\"zhangsan@example.com\"}");
}

private void createUser(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String email = request.getParameter("email");

response.setContentType("application/json;charset=" + encoding);
PrintWriter out = response.getWriter();
out.println("{\"success\":true,\"message\":\"用户创建成功\"}");
}

private void updateUser(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String id = request.getParameter("id");
String email = request.getParameter("email");

response.setContentType("application/json;charset=" + encoding);
PrintWriter out = response.getWriter();
out.println("{\"success\":true,\"message\":\"用户更新成功\"}");
}

private void deleteUser(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String id = request.getParameter("id");

response.setContentType("application/json;charset=" + encoding);
PrintWriter out = response.getWriter();
out.println("{\"success\":true,\"message\":\"用户删除成功\"}");
}

private void uploadAvatar(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

// 获取上传的文件
Part filePart = request.getPart("avatar");
String fileName = filePart.getSubmittedFileName();
long fileSize = filePart.getSize();

// 保存文件
String uploadPath = getServletContext().getRealPath("/uploads/");
java.io.File uploadDir = new java.io.File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}

filePart.write(uploadPath + java.io.File.separator + fileName);

response.setContentType("application/json;charset=" + encoding);
PrintWriter out = response.getWriter();
out.println("{\"success\":true,\"fileName\":\"" + fileName + "\",\"size\":" + fileSize + "}");
}
}

九、MVC 架构模式

9.1 MVC 模式概述

9.2 MVC 各层职责

层级 职责 组成部分
Model 业务逻辑和数据处理 Service、DAO、Entity
View 页面展示和用户交互 JSP、HTML、Thymeleaf
Controller 请求处理和流程控制 Servlet

9.3 简单 MVC 实现

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
* Model: User 实体类
*/
public class User {
private Long id;
private String username;
private String email;
private Integer status;
// getter/setter...
}

/**
* Model: UserDao 数据访问
*/
public class UserDao {

private static final Map<Long, User> userMap = new ConcurrentHashMap<>();

static {
userMap.put(1L, new User(1L, "zhangsan", "zhangsan@example.com", 1));
userMap.put(2L, new User(2L, "lisi", "lisi@example.com", 1));
}

public List<User> findAll() {
return new ArrayList<>(userMap.values());
}

public User findById(Long id) {
return userMap.get(id);
}

public User save(User user) {
if (user.getId() == null) {
user.setId((long) (userMap.size() + 1));
}
userMap.put(user.getId(), user);
return user;
}

public boolean deleteById(Long id) {
return userMap.remove(id) != null;
}
}

/**
* Model: UserService 业务逻辑
*/
public class UserService {

private UserDao userDao = new UserDao();

public List<User> getAllUsers() {
return userDao.findAll();
}

public User getUserById(Long id) {
if (id == null || id <= 0) {
throw new IllegalArgumentException("ID 不合法");
}
return userDao.findById(id);
}

public User createUser(User user) {
// 业务校验
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
return userDao.save(user);
}
}

/**
* Controller: UserController
*/
@WebServlet("/mvc/users/*")
public class UserController extends HttpServlet {

private UserService userService = new UserService();
private Gson gson = new Gson();

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

String pathInfo = request.getPathInfo();

if (pathInfo == null || pathInfo.equals("/")) {
// 列出所有用户
listUsers(request, response);
} else if (pathInfo.matches("/\\d+")) {
// 获取指定用户
Long id = Long.parseLong(pathInfo.substring(1));
getUser(request, response, id);
} else {
response.sendError(404, "Not Found");
}
}

@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

request.setCharacterEncoding("UTF-8");

String pathInfo = request.getPathInfo();

if ("/".equals(pathInfo)) {
createUser(request, response);
} else {
response.sendError(404, "Not Found");
}
}

private void listUsers(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
List<User> users = userService.getAllUsers();
sendJson(response, users);
}

private void getUser(HttpServletRequest request,
HttpServletResponse response, Long id)
throws ServletException, IOException {
try {
User user = userService.getUserById(id);
sendJson(response, user);
} catch (IllegalArgumentException e) {
response.sendError(404, e.getMessage());
}
}

private void createUser(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

BufferedReader reader = request.getReader();
String body = reader.lines().collect(Collectors.joining());
User user = gson.fromJson(body, User.class);

try {
User created = userService.createUser(user);
sendJson(response, created);
} catch (IllegalArgumentException e) {
response.sendError(400, e.getMessage());
}
}

private void sendJson(HttpServletResponse response, Object data)
throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(gson.toJson(data));
}
}

十、常见问题与最佳实践

10.1 常见问题与解决方案

10.2 最佳实践清单

实践 说明 推荐程度
使用注解配置 减少 XML 配置,代码更简洁 ✅✅✅
统一字符编码 过滤器中统一设置 UTF-8 ✅✅✅
使用 Session 存储敏感数据 重要数据放服务器端 ✅✅
合理使用转发与重定向 保持页面状态用转发,减少请求用重定向 ✅✅
线程安全 Servlet 是单实例多线程,注意成员变量 ✅✅✅
资源释放 关闭流、数据库连接等资源 ✅✅✅
日志记录 使用日志框架记录关键操作 ✅✅

10.3 线程安全注意事项

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* ❌ 错误示例:使用成员变量(线程不安全)
*/
@WebServlet("/unsafe")
public class UnsafeServlet extends HttpServlet {

private int count = 0; // ❌ 危险!所有请求共享这个变量

@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
count++; // ❌ 并发时计数错误
response.getWriter().write("访问次数:" + count);
}
}

/**
* ✅ 正确示例:使用局部变量(线程安全)
*/
@WebServlet("/safe")
public class SafeServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
int localCount = 0; // ✅ 每个请求独立的局部变量
localCount++;
response.getWriter().write("访问次数:" + localCount);
}
}

/**
* ✅ 正确示例:使用请求域属性(线程安全)
*/
@WebServlet("/safe2")
public class SafeServlet2 extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
// ✅ 使用请求域存储数据
request.setAttribute("count", 1);
Integer count = (Integer) request.getAttribute("count");
response.getWriter().write("访问次数:" + count);
}
}

/**
* ✅ 正确示例:使用同步块(需要共享数据时)
*/
@WebServlet("/synced")
public class SyncedServlet extends HttpServlet {

private int count = 0;

@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
synchronized (this) { // ✅ 加同步块
count++;
response.getWriter().write("访问次数:" + count);
}
}
}

十一、总结

11.1 核心知识点回顾

11.2 学习路线建议

11.3 下一步推荐学习

  • 📖 Spring MVC:Servlet 的高级封装,Java Web 开发主流框架
  • 📖 Spring Boot:简化 Spring 开发,快速构建 Web 应用
  • 📖 Tomcat 源码:深入理解 Servlet 容器的工作原理
  • 📖 **Servlet 4.0+**:HTTP/2、Server Push 等新特性

💡 写给读者的话:Servlet 是 Java Web 开发的基石,虽然现在很少有人直接手写 Servlet(大多数使用 Spring MVC 等框架),但理解 Servlet 的原理对于深入学习 Spring 框架和排查问题非常有帮助。”知其然,更知其所以然”,共勉!🚀


📅 本文首次发布于 2026 年 5 月 24 日