Servlet 详解
目录
一、引言
1.1 Servlet 技术发展史
Servlet(Server Applet,服务器小程序)是 Java 平台上用于构建动态 Web 应用的服务器端组件规范。其发展历程反映了 Java 企业级 Web 技术从无到有、从规范到生态的演进过程。
| 版本 | 发布时间 | 关键特性 |
|---|---|---|
| Servlet 1.0 | 1997 年 | 基础 CGI 替代方案,CGI 模式的同步处理 |
| Servlet 2.2 | 1999 年 | 引入 WAR 文件、独立 Web 应用模型 |
| Servlet 2.5 | 2005 年 | 注解支持简化部署描述 |
| Servlet 3.0 | 2009 年 | 注解配置、异步处理、可插拔特性 |
| Servlet 3.1 | 2013 年 | 非阻塞 IO 升级、HTTP 协议升级 |
| Servlet 4.0 | 2017 年 | HTTP/2 支持、服务器推送 |
| Servlet 5.0 | 2020 年 | Jakarta EE 迁移、命名空间切换 |
由上表可见,Servlet 规范历经二十余年演进,已具备现代 Web 服务所需的全部基础能力。
1.2 Servlet 在 Java EE 体系中的定位
Servlet 是 Java Enterprise Edition(Java EE,现为 Jakarta EE)体系中最早成熟的 Web 层规范,位于整个企业级 Java 架构的核心层级。
容器层作为请求入口与业务逻辑之间的桥梁,承担请求分发、协议解析、会话管理、安全控制等横切关注点。
1.3 学习 Servlet 的意义与价值
在 Spring、Spring Boot 等高级框架高度流行的当下,直接学习 Servlet 仍具备不可替代的价值:
- 理解底层原理:所有 Java Web 框架均基于 Servlet 构建,掌握底层有助于理解框架封装逻辑。
- 排查疑难问题:内存泄漏、线程安全、连接耗尽等问题,需要深入 Servlet 容器行为才能定位。
- 定制 Web 容器行为:过滤器、监听器、异步处理等高级特性是任何框架无法完全替代的能力。
- 维护遗留系统:金融、电信、政企领域的核心系统仍广泛运行 Servlet 规范的应用。
二、核心概念
2.1 Servlet 定义与生命周期
Servlet 是运行在 Web 服务器或应用服务器上的 Java 类,用于接收并响应来自 Web 客户端的请求。其生命周期由 Servlet 容器管理,包含加载类、实例化、初始化、服务、销毁五个阶段。
- 加载类:容器根据配置加载 Servlet 类字节码。
- 实例化:容器创建 Servlet 实例,仅执行一次。
- 初始化:调用
init方法,完成资源准备。 - 服务:调用
service方法,处理每一个客户端请求,重复执行。 - 销毁:调用
destroy方法,释放资源,仅执行一次。
2.2 Servlet 容器
Servlet 容器(Servlet Container)是实现 Servlet 规范的 Web 服务器组件,负责管理 Servlet 生命周期并提供请求路由、协议解析、安全控制等基础服务。
主流容器对照如下:
| 容器 | 维护方 | 特点 |
|---|---|---|
| Apache Tomcat | Apache 软件基金会 | 轻量级、生态成熟 |
| Jetty | Eclipse 基金会 | 高性能、嵌入式友好 |
| Undertow | Red Hat | 非阻塞 IO、低延迟 |
| WildFly | Red Hat | 全栈 Java EE 服务 |
2.3 HTTP 请求与响应模型
Servlet 基于 HTTP 协议实现客户端与服务器的交互。一次完整的交互包含请求消息与响应消息两个方向的数据流。
请求消息结构:
- 请求行:方法、URI、协议版本
- 请求头:键值对形式的元信息
- 空行:分隔头部与主体
- 消息体:表单数据或 JSON 等负载
响应消息结构:
- 状态行:协议版本、状态码、原因短语
- 响应头:服务器信息、内容类型等
- 空行:分隔头部与主体
- 消息体:HTML、JSON、二进制等响应内容
| 方法 | 语义 | 幂等性 |
|---|---|---|
| GET | 获取资源 | 是 |
| POST | 提交数据 | 否 |
| PUT | 更新资源 | 是 |
| DELETE | 删除资源 | 是 |
2.4 Servlet API 主要接口
Servlet API 由 javax.servlet 与 javax.servlet.http 两个核心包组成,关键接口如下:
实际开发中最常使用的接口组合:
HttpServlet:HTTP 协议专用基类HttpServletRequest:封装 HTTP 请求HttpServletResponse:封装 HTTP 响应ServletConfig:初始化配置ServletContext:全局上下文
三、详细说明
3.1 Servlet 生命周期详解
Servlet 生命周期由容器严格管控。理解各阶段的执行时机与异常处理策略,是开发高可靠 Web 应用的基础。
生命周期关键方法:
- 构造器:仅在实例化阶段执行一次。
init(ServletConfig):完成数据库连接、缓存预热等初始化操作。service(ServletRequest, ServletResponse):每个请求调用一次。destroy():容器关闭或重新部署时调用,用于释放文件句柄、关闭连接池。<load-on-startup>:通过web.xml配置项可控制 Servlet 在容器启动时即初始化。
3.2 请求处理机制
Servlet 接收到请求后,由 service 方法根据 HTTP 方法类型分派到对应的 doXxx 方法。开发者通常重写 doGet、doPost 等方法实现业务逻辑。
请求处理流程:
请求处理中的关键对象:
- 请求参数:通过
getParameter获取查询字符串或表单字段。 - 请求属性:通过
setAttribute与getAttribute在组件间传递数据。 - 请求头:通过
getHeader获取 Cookie、User-Agent 等元信息。 - 响应输出:通过
getWriter或getOutputStream写入响应内容。 - 中文编码:通过
setCharacterEncoding与setContentType协同处理。
3.3 会话管理
HTTP 协议本身是无状态的,会话管理用于在多次请求间保持用户状态。Servlet 规范提供四种会话跟踪机制。
| 机制 | 实现原理 | 优点 | 缺点 |
|---|---|---|---|
| Cookie | 客户端存储小段文本 | 实现简单、可禁用 | 可被篡改、大小受限 |
| HttpSession | 服务端维护会话对象 | 安全可靠 | 占用服务端内存 |
| URL 重写 | 将会话 ID 拼接到 URL | 兼容禁用 Cookie 场景 | URL 冗长、外泄风险 |
| 隐藏表单域 | 通过表单隐藏字段传递 | 简单可控 | 仅适用于表单提交 |
HttpSession 常用方法:
setAttribute(String, Object):存储会话属性。getAttribute(String):获取会话属性。removeAttribute(String):移除会话属性。invalidate():销毁当前会话。setMaxInactiveInterval(int):设置超时时间,单位秒。
3.4 过滤器与监听器
过滤器(Filter)是 Servlet 规范定义的组件级拦截机制,可在请求到达 Servlet 之前或响应返回客户端之前执行预处理与后处理逻辑。
过滤器典型应用场景:
- 字符编码统一设置
- 用户身份认证与权限校验
- 请求日志与审计
- XSS、SQL 注入等安全过滤
- 响应数据压缩与缓存
监听器(Listener)用于监听 Web 应用生命周期事件与对象状态变化,主要接口包括:
ServletContextListener:监听应用启动与关闭。HttpSessionListener:监听会话创建与销毁。ServletRequestListener:监听请求创建与销毁。HttpSessionAttributeListener:监听会话属性变更。
监听器与过滤器协同工作,可实现完整的横切关注点处理。
3.5 异步处理与 WebSocket
Servlet 3.0 起引入异步处理机制,允许 Servlet 在等待耗时操作时释放容器线程,提升高并发场景下的吞吐量。
异步处理关键 API:
AsyncContext:异步上下文对象。request.startAsync():开启异步处理。asyncContext.complete():标记异步处理完成。asyncContext.dispatch():将响应分派到指定资源。
WebSocket(Servlet 3.1+ 支持)提供全双工通信能力,适用于实时聊天、行情推送、协同编辑等场景。Servlet 4.0 起新增 HTTP/2 服务器推送能力,可由服务端主动向客户端推送关联资源。
四、示例
4.1 Hello World 示例
最基础的 Servlet 实现,演示请求响应全流程。
Step 1:编写 Servlet 类
文件路径:src/main/java/com/example/HelloServlet.java
package com.example;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head><title>Hello Servlet</title></head>");
out.println("<body>");
out.println("<h1>欢迎学习 Servlet</h1>");
out.println("<p>当前时间:" + new java.util.Date() + "</p>");
out.println("</body>");
out.println("</html>");
}
}
Step 2:注册 Servlet
文件路径:src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
version="3.1">
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
Step 3:访问验证
部署至 Tomcat 后访问 http://localhost:8080/hello,页面将显示欢迎信息与当前时间。
4.2 表单处理示例
演示登录表单的提交与参数读取。
Step 1:登录页面
文件路径:src/main/webapp/login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<form action="login" method="post">
<p>
<label>用户名:</label>
<input type="text" name="username" required>
</p>
<p>
<label>密码:</label>
<input type="password" name="password" required>
</p>
<p>
<button type="submit">登录</button>
</p>
</form>
</body>
</html>
Step 2:处理 Servlet
文件路径:src/main/java/com/example/LoginServlet.java
package com.example;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
PrintWriter out = response.getWriter();
if ("admin".equals(username) && "123456".equals(password)) {
out.println("<h1>登录成功</h1>");
out.println("<p>欢迎," + username + "</p>");
} else {
out.println("<h1>登录失败</h1>");
out.println("<p>用户名或密码错误</p>");
}
}
}
4.3 数据库操作示例
演示通过 Servlet 查询数据库并渲染结果列表。
Step 1:数据访问对象
文件路径:src/main/java/com/example/UserDao.java
package com.example;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class UserDao {
private static final String URL = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8";
private static final String USER = "root";
private static final String PASSWORD = "root";
public List<String> findAllUsernames() throws Exception {
List<String> usernames = new ArrayList<>();
Class.forName("com.mysql.jdbc.Driver");
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement ps = conn.prepareStatement("SELECT username FROM users");
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
usernames.add(rs.getString("username"));
}
}
return usernames;
}
}
Step 2:用户列表 Servlet
文件路径:src/main/java/com/example/UserListServlet.java
package com.example;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UserListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
UserDao dao = new UserDao();
List<String> usernames = dao.findAllUsernames();
out.println("<h1>用户列表</h1>");
out.println("<ul>");
for (String name : usernames) {
out.println("<li>" + name + "</li>");
}
out.println("</ul>");
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
out.println("<p>查询失败:" + e.getMessage() + "</p>");
}
}
}
4.4 过滤器示例
演示字符编码过滤器与日志过滤器的实现。
Step 1:字符编码过滤器
文件路径:src/main/java/com/example/EncodingFilter.java
package com.example;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class EncodingFilter implements Filter {
private String encoding = "UTF-8";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String configured = filterConfig.getInitParameter("encoding");
if (configured != null) {
this.encoding = configured;
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding(encoding);
response.setCharacterEncoding(encoding);
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
Step 2:日志过滤器
文件路径:src/main/java/com/example/LoggingFilter.java
package com.example;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class LoggingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
long startTime = System.currentTimeMillis();
HttpServletRequest httpRequest = (HttpServletRequest) request;
System.out.println("[请求开始] " + httpRequest.getMethod() + " "
+ httpRequest.getRequestURI());
chain.doFilter(request, response);
long duration = System.currentTimeMillis() - startTime;
System.out.println("[请求结束] " + httpRequest.getRequestURI()
+ " 耗时 " + duration + " 毫秒");
}
@Override
public void destroy() {
}
}
Step 3:过滤器配置
文件路径:src/main/webapp/WEB-INF/web.xml
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>com.example.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.example.LoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
4.5 会话管理示例
演示用户登录后通过会话保持身份,并实现访问受限资源。
Step 1:会话登录 Servlet
文件路径:src/main/java/com/example/SessionLoginServlet.java
package com.example;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionLoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
if ("admin".equals(username) && "123456".equals(password)) {
HttpSession session = request.getSession();
session.setAttribute("currentUser", username);
session.setMaxInactiveInterval(1800);
response.sendRedirect("dashboard");
} else {
response.getWriter().println("登录失败");
}
}
}
Step 2:会话校验过滤器
文件路径:src/main/java/com/example/AuthFilter.java
package com.example;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
boolean loggedIn = session != null && session.getAttribute("currentUser") != null;
boolean requiresAuth = httpRequest.getRequestURI().contains("/dashboard");
if (requiresAuth && !loggedIn) {
httpResponse.sendRedirect("login.html");
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
Step 3:仪表板 Servlet
文件路径:src/main/java/com/example/DashboardServlet.java
package com.example;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class DashboardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
HttpSession session = request.getSession(false);
String currentUser = (String) session.getAttribute("currentUser");
PrintWriter out = response.getWriter();
out.println("<h1>仪表板</h1>");
out.println("<p>当前登录用户:" + currentUser + "</p>");
out.println("<p>会话 ID:" + session.getId() + "</p>");
}
}
五、最佳实践
5.1 线程安全
Servlet 容器默认采用单实例多线程模型,每个 Servlet 类只有一个实例,由多个请求线程并发调用。这要求开发者严格遵守线程安全规范。
线程安全核心原则:
- 避免实例变量:所有请求级数据应通过方法参数、局部变量或
HttpServletRequest属性传递。 - 谨慎使用共享对象:连接池、线程池等共享对象必须是线程安全的实现。
- 同步锁最小化:仅在必要时使用
synchronized,避免锁粒度过粗导致性能下降。 - 不可变对象优先:返回值与配置数据优先设计为不可变对象。
线程安全常见误区:
- 将用户数据存入 Servlet 实例字段。
- 在
init方法中维护请求级缓存。 - 使用非线程安全的集合类(如
ArrayList、HashMap)作为共享状态。
5.2 性能优化
性能优化贯穿 Servlet 设计、编码、部署各阶段,关键措施包括:
连接与资源管理:
- 使用数据库连接池(如 HikariCP、Druid)避免频繁创建连接。
- 及时关闭 IO 流与数据库结果集。
- 合理配置连接池参数,匹配业务并发量。
响应内容压缩:
- 通过过滤器实现 Gzip 压缩。
- 静态资源启用 HTTP 缓存。
线程与并发:
- 耗时操作采用异步处理。
- 关键业务利用线程池并发执行。
容器参数调优:
- 调整 Tomcat 线程池最大线程数。
- 合理设置 JVM 堆内存。
- 启用 NIO 连接器。
5.3 安全防护
Servlet 应用直面客户端,安全防护至关重要,必须从认证、授权、输入校验、会话管理、传输加密五个层面建立纵深防御。
| 防护层次 | 措施 | 实现方式 |
|---|---|---|
| 认证 | 验证用户身份 | 表单登录、Token 验证、SSO 集成 |
| 授权 | 控制资源访问 | 基于角色、基于 URL、注解式 |
| 输入校验 | 过滤恶意输入 | 参数白名单、转义、预编译语句 |
| 会话安全 | 防止会话劫持 | HTTPS、HttpOnly、Secure 标志 |
| 传输加密 | 防止窃听篡改 | TLS 1.2+、HSTS、证书钉扎 |
常见 Web 攻击与防御:
- 跨站脚本(XSS):对输出内容进行 HTML 转义,使用 OWASP Java Encoder。
- 跨站请求伪造(CSRF):采用同步令牌或 SameSite Cookie 策略。
- SQL 注入:使用
PreparedStatement,严格校验参数。 - 会话固定:登录成功后调用
session.invalidate()重建会话。 - 不安全反序列化:避免反序列化不可信数据,使用白名单校验。
六、总结
6.1 核心要点回顾
本文系统讲解了 Servlet 技术体系,关键要点归纳如下:
- Servlet 是 Java Web 应用的底层组件规范,所有上层框架均构建于其上。
- Servlet 生命周期由容器管理,包含加载、实例化、初始化、服务、销毁五个阶段。
- 请求处理通过
service方法分派至具体doXxx方法,需关注中文编码与异常处理。 - 会话管理依赖
HttpSession、Cookie、URL 重写等机制,需结合安全性综合考量。 - 过滤器与监听器提供横切关注点处理能力,是认证、日志、压缩等功能的标准实现。
- 异步处理、WebSocket、HTTP/2 等高级特性满足现代 Web 应用的多样化需求。
- 线程安全、性能优化、安全防护是 Servlet 应用开发必须长期关注的三项基础工程。
6.2 进阶学习路径
掌握基础概念后,可按以下路径继续深入:
推荐学习资源:
- 官方规范:Jakarta Servlet 5.0 Specification。
- 容器源码:Apache Tomcat 源码,重点阅读 Connector 与 Container 模块。
- 优秀书籍:《Head First Servlet & JSP》、《Servlet 3.1 规范中文版》。
- 实战项目:基于 Servlet 实现简易 MVC 框架,深入理解请求分发与视图解析。
6.3 现代 Web 开发的演进
Servlet 规范在演进,Web 开发模式也在持续变革。当前主流趋势如下:
| 技术趋势 | 核心思想 | 与 Servlet 的关系 |
|---|---|---|
| 注解驱动 | 通过注解替代 XML 配置 | Servlet 3.0+ 已支持 |
| 嵌入式容器 | 应用与容器一体化打包 | Spring Boot 内嵌 Tomcat |
| 响应式编程 | 非阻塞、事件驱动 | Servlet 3.1+ 提供非阻塞 IO |
| 云原生 | 容器化部署、弹性伸缩 | 镜像化 Servlet 应用 |
| 微服务 | 单一职责、轻量通信 | Servlet 提供 HTTP 基础能力 |
Servlet 作为 Java Web 技术体系的基石,既未被时代淘汰,也未停滞不前。理解其设计思想与运行机制,是掌握任何现代 Java Web 框架的必经之路。
文档结束

浙公网安备 33010602011771号