Article

请求处理 Jakarta Servlet

更新于:2026-07-15

第一章:Jakarta Servlet 概述

1.1 什么是 Jakarta Servlet

概念名称说明注意事项
Jakarta Servlet是运行在 Web 容器(如 Tomcat)中的 Java 服务器端组件,用于处理 HTTP 请求并生成动态响应。它是 Jakarta EE 规范的一部分,定义了标准的请求-响应编程模型。必须由容器管理生命周期;不能直接通过 main 方法运行。
Servlet 容器提供网络服务、请求解码、响应格式化、线程管理及安全控制的运行环境(如 Apache Tomcat、Jetty)。容器负责加载、初始化、调用和销毁 Servlet 实例。所有 Servlet 必须部署在兼容的容器中才能运行。
核心接口所有 Servlet 必须实现 jakarta.servlet.Servlet 接口,该接口定义了 init()service()destroy() 等生命周期方法。实际开发中通常继承 HttpServlet 而非直接实现接口。
命名空间变更自 Jakarta EE 9 起,原 javax.servlet 包更名为 jakarta.servlet,这是因 Oracle 将 Java EE 移交 Eclipse 基金会后品牌变更所致。使用 Spring Boot 3.0+ 或 Jakarta EE 9+ 项目时必须使用 jakarta.* 包,否则会报类找不到错误。

1.2 Jakarta Servlet 与 Java EE / Jakarta EE 的关系

概念名称说明注意事项
Java EEJava Platform, Enterprise Edition,是 Sun/Oracle 主导的企业级 Java 应用规范集合,包含 Servlet、JSP、EJB、JPA、JMS 等技术。Java EE 8 是最后一个由 Oracle 发布的版本。
Jakarta EEJava EE 移交 Eclipse 基金会后的继任者,自 2017 年起由社区维护,名称变更为 Jakarta EE,包名从 javax 改为 jakartaJakarta EE 9 = Java EE 8 功能 + 包名变更;Jakarta EE 10+ 引入新特性。
Servlet 在规范中的地位Servlet 是 Jakarta EE 最基础、最核心的 Web 层规范,其他框架(如 Spring MVC、JSF)均构建在其之上。即使使用高级框架,底层仍依赖 Servlet API 处理请求。
版本演进关系- Servlet 4.0 → Java EE 8
- Servlet 5.0 → Jakarta EE 8(仅包名变更)
- Servlet 6.0 → Jakarta EE 10(新增功能)
- Servlet 6.1 → Jakarta EE 10.1(增强 API)
开发时需确保 Servlet API 版本与容器(如 Tomcat 10+)兼容。

1.3 Jakarta Servlet 的核心作用与典型应用场景

场景名称作用说明典型应用示例注意事项
动态网页生成根据用户请求动态组装 HTML 内容,替代静态页面。可结合 JSP、Thymeleaf 等模板引擎。用户登录后显示个性化首页;商品列表页实时渲染。需设置正确的 Content-Type(如 text/html;charset=UTF-8)。
RESTful API 开发接收 JSON/XML 请求,执行业务逻辑,返回结构化数据(如 JSON),供前端或移动端调用。用户注册接口 /api/register;订单查询接口 /api/orders/{id}响应头应设为 application/json,并处理 CORS(跨域)问题。
微服务通信基础作为微服务的 HTTP 入口,接收服务间调用请求,常与 Spring Boot、Quarkus 等框架结合。订单服务调用库存服务的 /inventory/check 接口。需考虑高并发、超时、重试等分布式问题。
身份认证与授权通过 Filter 或 Servlet 拦截请求,验证 Token 或 Session,实现统一安全控制。登录拦截器;JWT 验证中间件。敏感操作需配合 HTTPS,防止会话劫持。
文件上传与下载利用 HttpServletRequest.getParts()(Servlet 3.0+)处理 multipart 表单,实现文件接收与分发。用户头像上传;报表导出下载。需配置 @MultipartConfig 注解或 web.xml 中的 <multipart-config>
单点登录(SSO)支撑作为 SSO 系统的回调端点,处理认证中心重定向回来的票据(ticket)。企业内部多个系统共享一套登录状态。需严格校验票据有效性,防止伪造攻击。

第二章:开发环境搭建

2.1 JDK 与 Maven 环境准备

步骤名称操作细节注意事项
安装 JDK(推荐 JDK 17 或 21)1. 访问 Oracle JDK 或 Eclipse Temurin 下载 LTS 版本。
2. 运行安装程序,选择纯英文路径(如 D:\Java\jdk-21)。
3. 配置系统环境变量:
  - 新建 JAVA_HOME = D:\Java\jdk-21
  - 在 Path 中添加 %JAVA_HOME%\bin
路径中禁止包含空格或中文;Jakarta Servlet 6.0+ 要求 JDK 11+,推荐使用 JDK 17/21。
验证 JDK 安装打开命令行,执行:
java -version
javac -version
应输出对应版本信息(如 openjdk version "21.0.2")。
若提示”不是内部或外部命令”,说明 Path 配置错误,需重启终端或系统。
安装 Maven1. 下载 Apache Maven(如 apache-maven-3.9.12-bin.zip)。
2. 解压至目录(如 D:\Maven\apache-maven-3.9.12)。
3. 配置环境变量:
  - 新建 MAVEN_HOME = D:\Maven\apache-maven-3.9.12
  - 在 Path 中添加 %MAVEN_HOME%\bin
不要将 Maven 放在 C 盘或带空格路径;建议使用 3.8+ 版本以兼容 Jakarta EE。
配置 Maven 本地仓库与镜像1. 在 Maven 根目录新建 repo 文件夹。
2. 编辑 conf/settings.xml
  - 取消注释 <localRepository> 并设为 <localRepository>D:\Maven\repo</localRepository>
  - 在 <mirrors> 中添加阿里云镜像:
    <mirror><id>aliyun</id><mirrorOf>*</mirrorOf><url>https://maven.aliyun.com/repository/public</url></mirror>
镜像加速依赖下载;避免使用已废弃的 nexus/content/groups/public 地址。
验证 Maven 安装执行命令:
mvn -v
应显示 Maven 版本、Java 版本及 MAVEN_HOME 路径。
若报错”JAVA_HOME not found”,需确认 JDK 环境变量是否生效。

2.2 Web 容器选择(Tomcat、Jetty 等)

容器名称用途说明适用场景注意事项
Apache Tomcat最流行的开源 Servlet 容器,支持 Jakarta Servlet、JSP、WebSocket 等规范。企业级 Web 应用、传统 MVC 架构、学习入门。Tomcat 10+ 支持 Jakarta Servlet 5.0+(包名为 jakarta.*);Tomcat 9 仅支持 javax.*
Eclipse Jetty轻量级、嵌入式友好、模块化设计的 Web 容器,启动快、内存占用低。微服务、测试环境、嵌入式部署、WebSocket 密集型应用。适合与 Spring Boot 集成;可通过代码直接启动服务器实例。
Undertow由 Red Hat 开发,基于 NIO 的高性能容器,支持阻塞与非阻塞 IO 混合模型。高并发 API 网关、低延迟服务、云原生微服务。常用于替换 Spring Boot 默认 Tomcat;单实例 TPS 高于 Tomcat。
选择建议- 学习/通用开发 → Tomcat 10+
- 嵌入式/轻量 → Jetty
- 高性能/微服务 → Undertow
必须确保容器版本与 Jakarta Servlet 规范版本匹配(如 Servlet 6.0 需 Tomcat 10.1+)。切勿混用 javaxjakarta 包,否则运行时抛 ClassNotFoundException

2.3 创建第一个 Jakarta Servlet 项目(Maven + WAR)

步骤名称操作细节注意事项
创建 Maven 项目在命令行执行:
mvn archetype:generate -DgroupId=com.example -DartifactId=jakarta-demo -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
生成标准 WAR 项目结构。
使用 maven-archetype-webapp 可快速生成 web.xml 和目录结构。
修改 pom.xml 支持 Jakarta Servlet添加以下依赖(以 Servlet 6.0 为例):

xml<br/><properties><br/> <maven.compiler.source>17</maven.compiler.source><br/> <maven.compiler.target>17</maven.compiler.target><br/> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><br/></properties><br/><dependencies><br/> <dependency><br/> <groupId>jakarta.servlet</groupId><br/> <artifactId>jakarta.servlet-api</artifactId><br/> <version>6.0.0</version><br/> <scope>provided</scope><br/> </dependency><br/></dependencies><br/>

设置打包类型为 <packaging>war</packaging>
scope 必须为 provided,因容器已提供实现;版本需与目标容器兼容(如 Tomcat 10.1 对应 Servlet 6.0)。
编写 HelloServletsrc/main/java/com/example/HelloServlet.java 中编写:

java<br/>package com.example;<br/>import jakarta.servlet.*;<br/>import jakarta.servlet.http.*;<br/>import java.io.*;<br/>public class HelloServlet extends HttpServlet {<br/> protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {<br/> resp.setContentType("text/html;charset=UTF-8");<br/> PrintWriter out = resp.getWriter();<br/> out.println("<h1>Hello, Jakarta Servlet!</h1>");<br/> }<br/>}<br/>
必须继承 HttpServlet;方法签名需抛出 ServletExceptionIOException
配置 Servlet 映射(注解方式)HelloServlet 类上添加注解:
@WebServlet("/hello")
无需修改 web.xml(要求 Servlet 3.0+)。
若使用 web.xml,需在 <web-app> 中添加 <servlet><servlet-mapping>,且 metadata-complete="false"
打包与部署1. 执行 mvn clean package 生成 target/jakarta-demo.war
2. 将 WAR 文件复制到 Tomcat 的 webapps/ 目录。
3. 启动 Tomcat(bin/startup.batstartup.sh)。
4. 浏览器访问 http://localhost:8080/jakarta-demo/hello
WAR 文件名即上下文路径;若部署失败,检查日志 logs/catalina.outlocalhost.log
验证运行结果页面应显示:
<h1>Hello, Jakarta Servlet!</h1>
若出现 404,检查 URL 路径、Servlet 映射及 Tomcat 是否加载应用成功。

第三章:Servlet 核心接口与类

3.1 jakarta.servlet.Servlet 接口详解

方法名称语法用途代码示例注意事项
initvoid init(ServletConfig config) throws ServletException初始化 Servlet 实例,仅在生命周期开始时调用一次。可通过 config 获取初始化参数或 ServletContextjava<br/>@Override<br/>public void init(ServletConfig config) throws ServletException {<br/> String dbUrl = config.getInitParameter("dbUrl");<br/> System.out.println("DB URL: " + dbUrl);<br/>}<br/>若抛出 ServletException,容器将标记该 Servlet 不可用;通常调用 super.init(config) 以保存配置引用。
getServletConfigServletConfig getServletConfig()返回当前 Servlet 的配置对象(由容器在 init 中传入)。java<br/>ServletConfig cfg = getServletConfig();<br/>String param = cfg.getInitParameter("timeout");<br/>init 调用前调用可能返回 nullGenericServlet 已实现此方法并缓存配置。
servicevoid service(ServletRequest req, ServletResponse res) throws ServletException, IOException处理客户端请求的核心方法,每次 HTTP 请求都会触发。java<br/>@Override<br/>public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {<br/> res.getWriter().write("Handled by custom service");<br/>}<br/>开发者通常不直接重写此方法,而是继承 HttpServlet 并重写 doGet/doPost 等。
getServletInfoString getServletInfo()返回关于 Servlet 的描述信息(如作者、版本、版权等)。java<br/>@Override<br/>public String getServletInfo() {<br/> return "HelloServlet v1.0 by Example Inc.";<br/>}<br/>默认返回空字符串;常用于管理或监控工具展示元数据。
destroyvoid destroy()在 Servlet 被销毁前释放资源(如关闭数据库连接、清理缓存),仅调用一次。java<br/>@Override<br/>public void destroy() {<br/> if (dbConnection != null) dbConnection.close();<br/> System.out.println("Servlet destroyed");<br/>}<br/>容器保证在所有请求线程结束后才调用;不可在此方法中启动新线程。

💡 说明:所有 Jakarta EE 9+ 项目必须使用 jakarta.servlet.Servlet,而非旧版 javax.servlet.Servlet

3.2 GenericServlet 抽象类

方法/特性语法/说明用途代码示例注意事项
继承关系public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable提供与协议无关的 Servlet 基础实现,简化开发。public class MyServlet extends GenericServlet { ... }适用于非 HTTP 协议场景(极少使用);Web 开发应优先使用 HttpServlet
service(抽象)public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException必须由子类实现,用于处理请求。java<br/>@Override<br/>public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {<br/> res.setContentType("text/plain");<br/> res.getWriter().println("Generic response");<br/>}<br/>是唯一必须重写的方法;其他生命周期方法已有默认空实现。
logpublic void log(String msg)
public void log(String message, Throwable t)
向容器日志系统记录消息或异常。java<br/>log("User accessed generic servlet");<br/>log("Error occurred", e);<br/>日志格式和输出位置由容器决定(如 Tomcat 的 catalina.out)。
getInitParameterpublic String getInitParameter(String name)直接获取初始化参数,无需先获取 ServletConfigString timeout = getInitParameter("timeout");内部调用 getServletConfig().getInitParameter(name),若未初始化则抛 NullPointerException
getServletContextpublic ServletContext getServletContext()获取 Web 应用上下文对象。java<br/>ServletContext ctx = getServletContext();<br/>String appPath = ctx.getContextPath();<br/>仅在 init 调用后可用;可用于共享应用级数据。

⚠️ 注意GenericServlet 不处理 HTTP 方法分发,无法区分 GET/POST,因此不适用于标准 Web 开发。

3.3 HttpServlet 抽象类及其常用方法

方法名称语法用途代码示例注意事项
doGetprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException处理 HTTP GET 请求,常用于查询、页面展示等幂等操作。java<br/>@Override<br/>protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {<br/> resp.setContentType("text/html;charset=UTF-8");<br/> resp.getWriter().println("<h1>Welcome</h1>");<br/>}<br/>浏览器地址栏访问、超链接点击均触发此方法;避免在此执行修改数据的操作。
doPostprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException处理 HTTP POST 请求,常用于表单提交、文件上传等非幂等操作。java<br/>@Override<br/>protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {<br/> String username = req.getParameter("user");<br/> resp.getWriter().println("Hello " + username);<br/>}<br/>请求体可携带大量数据;需处理中文乱码(建议在 Filter 中统一设置 req.setCharacterEncoding("UTF-8"))。
doPutprotected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException处理 HTTP PUT 请求,常用于更新资源(RESTful 风格)。java<br/>@Override<br/>protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {<br/> resp.setStatus(HttpServletResponse.SC_OK);<br/>}<br/>需前端显式发送 PUT 请求(如 AJAX 或 Postman);浏览器表单不支持 PUT。
doDeleteprotected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException处理 HTTP DELETE 请求,常用于删除资源(RESTful 风格)。java<br/>@Override<br/>protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {<br/> String id = req.getParameter("id");<br/> // delete resource by id<br/> resp.setStatus(HttpServletResponse.SC_NO_CONTENT);<br/>}<br/>同 PUT,需程序化发起请求;响应通常返回 204(无内容)。
service(已实现)protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException根据 HTTP 方法自动分发到 doGetdoPost 等方法。无需重写;内部逻辑:
if (method.equals("GET")) doGet(...);
else if (method.equals("POST")) doPost(...);
若重写此方法,将覆盖自动分发机制,需自行处理方法路由。
doHead / doOptions / doTrace对应 HTTP 方法的处理方法处理 HEAD、OPTIONS、TRACE 请求(较少使用)。通常保留默认实现;doHead 默认调用 doGet 但不输出 body。TRACE 方法存在安全风险(XST 攻击),生产环境应禁用。

最佳实践

  • 实际开发中几乎总是继承 HttpServlet
  • 重写 doGetdoPost 即可满足 95% 以上需求。
  • 若需支持多种方法,可在 doPost 中调用 doGet(req, resp) 实现复用。

第四章:请求与响应处理

4.1 HttpServletRequest 常用方法

方法名称语法用途代码示例注意事项
getMethodString getMethod()获取 HTTP 请求方法(如 GET、POST)。String method = req.getMethod(); // "GET"常用于判断请求类型以执行不同逻辑。
getRequestURIString getRequestURI()获取请求的 URI(不含协议、主机、端口和查询字符串)。String uri = req.getRequestURI(); // "/app/hello"不包含上下文路径以外的协议信息。
getRequestURLStringBuffer getRequestURL()获取完整的请求 URL(含协议、主机、端口、路径)。String url = req.getRequestURL().toString(); // "http://localhost:8080/app/hello"返回 StringBuffer,可追加修改。
getContextPathString getContextPath()获取 Web 应用的上下文路径(部署名)。String ctx = req.getContextPath(); // "/myapp"根应用返回空字符串 "",非 /
getHeaderString getHeader(String name)获取指定请求头的值(如 User-AgentReferer)。String ua = req.getHeader("User-Agent");头名不区分大小写;若不存在返回 null
getHeadersEnumeration<String> getHeaders(String name)获取同名请求头的所有值(适用于重复头字段)。Enumeration<String> cookies = req.getHeaders("Cookie");少数头(如 Cookie)可能多次出现。
getHeaderNamesEnumeration<String> getHeaderNames()获取所有请求头名称的枚举。java<br/>Enumeration<String> names = req.getHeaderNames();<br/>while (names.hasMoreElements()) {<br/> String h = names.nextElement();<br/>}<br/>用于调试或日志记录。
getRemoteAddrString getRemoteAddr()获取客户端 IP 地址。String ip = req.getRemoteAddr(); // "127.0.0.1"若经过代理,需检查 X-Forwarded-For 头获取真实 IP。
getProtocolString getProtocol()获取请求使用的协议及版本。String proto = req.getProtocol(); // "HTTP/1.1"可用于判断是否支持 HTTP/2(但 Servlet 6.0+ 才有明确支持)。
getSessionHttpSession getSession()
HttpSession getSession(boolean create)
获取当前会话对象,若不存在则创建(或根据参数决定)。java<br/>HttpSession session = req.getSession();<br/>session.setAttribute("user", "alice");<br/>默认 create=true;高并发下注意会话管理开销。

4.2 HttpServletResponse 常用方法

方法名称语法用途代码示例注意事项
setStatusvoid setStatus(int sc)设置 HTTP 响应状态码(如 200、302、404)。resp.setStatus(404);仅用于成功或重定向类状态;错误状态建议用 sendError
sendErrorvoid sendError(int sc)
void sendError(int sc, String msg)
发送错误响应(自动设置状态码并生成错误页面)。resp.sendError(404, "Page not found");调用后响应即结束,不可再写入 body;容器可能覆盖自定义消息。
setHeadervoid setHeader(String name, String value)设置响应头(覆盖同名头)。resp.setHeader("Cache-Control", "no-cache");常用于控制缓存、CORS、文件下载等。
addHeadervoid addHeader(String name, String value)添加响应头(保留已有同名头)。java<br/>resp.addHeader("X-Custom", "value1");<br/>resp.addHeader("X-Custom", "value2");<br/>适用于允许多值的头(如 Set-Cookie)。
setIntHeader / addIntHeadervoid setIntHeader(String name, int value)
void addIntHeader(String name, int value)
设置整数值的响应头。resp.setIntHeader("Retry-After", 3600);自动转换为字符串;避免手动拼接数字。
setContentTypevoid setContentType(String type)设置响应内容的 MIME 类型和字符编码。java<br/>resp.setContentType("text/html;charset=UTF-8");<br/>resp.setContentType("application/json");<br/>必须在 getWriter() 前调用,否则无效。
setCharacterEncodingvoid setCharacterEncoding(String charset)单独设置响应字符编码(优先级高于 setContentType 中的编码)。resp.setCharacterEncoding("UTF-8");同样需在 getWriter() 前设置。
getWriterPrintWriter getWriter()获取字符输出流,用于发送文本响应(如 HTML、JSON)。java<br/>PrintWriter out = resp.getWriter();<br/>out.println("<h1>Hello</h1>");<br/>getOutputStream() 互斥,不可同时使用。
getOutputStreamServletOutputStream getOutputStream()获取字节输出流,用于发送二进制数据(如图片、文件)。java<br/>ServletOutputStream os = resp.getOutputStream();<br/>os.write(fileBytes);<br/>适用于文件下载、PDF 生成等场景。
sendRedirectvoid sendRedirect(String location)发送 302 重定向响应,跳转到新 URL。java<br/>resp.sendRedirect("/login.jsp");<br/>resp.sendRedirect("https://example.com");<br/>URL 可为相对或绝对;浏览器地址栏会改变。

4.3 请求参数获取与响应内容输出

操作类型方法名称语法用途代码示例注意事项
获取单值参数getParameterString getParameter(String name)获取指定名称的请求参数值(取第一个)。String user = req.getParameter("username");若参数不存在返回 null;适用于普通表单字段。
获取多值参数getParameterValuesString[] getParameterValues(String name)获取同名参数的所有值(如复选框)。String[] hobbies = req.getParameterValues("hobby");若无此参数返回 null;若有单值则数组长度为 1。
获取所有参数名getParameterNamesEnumeration<String> getParameterNames()枚举所有参数名称。Enumeration<String> names = req.getParameterNames();用于动态遍历参数。
获取参数 MapgetParameterMapMap<String, String[]> getParameterMap()获取所有参数的键值对(值为字符串数组)。java<br/>Map<String, String[]> map = req.getParameterMap();<br/>for (String key : map.keySet()) { ... }<br/>只读 Map,不可修改;常用于日志或验证。
输出文本响应getWriter + printlnPrintWriter out = resp.getWriter(); out.println(content);向客户端发送 HTML、JSON 等文本内容。java<br/>resp.setContentType("application/json");<br/>PrintWriter out = resp.getWriter();<br/>out.print("{\"status\":\"ok\"}");<br/>必须先设 Content-Type 和编码。
输出二进制响应getOutputStream + writeServletOutputStream os = resp.getOutputStream(); os.write(bytes);发送图片、PDF、ZIP 等二进制文件。java<br/>resp.setContentType("image/png");<br/>resp.setContentLength(imageBytes.length);<br/>os.write(imageBytes);<br/>需设置 Content-Length 以优化传输。

4.4 字符编码与中文乱码处理

问题场景解决方案操作细节代码示例注意事项
POST 请求中文乱码在读取参数前设置请求编码调用 req.setCharacterEncoding("UTF-8")java<br/>@Override<br/>protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {<br/> req.setCharacterEncoding("UTF-8");<br/> String name = req.getParameter("name"); // 正常中文<br/>}<br/>必须在 getParameter() 前调用;仅对 POST 有效(因 GET 参数由 URL 编码决定)。
GET 请求中文乱码修改 Tomcat 配置或手动解码方式一:在 server.xml<Connector> 中添加 URIEncoding="UTF-8"
方式二:手动重新编码:new String(value.getBytes("ISO-8859-1"), "UTF-8")
java<br/>String raw = req.getParameter("q");<br/>String decoded = new String(raw.getBytes("ISO-8859-1"), "UTF-8");<br/>推荐使用方式一;方式二易出错且不通用。
响应中文乱码设置响应内容类型和编码调用 resp.setContentType("text/html;charset=UTF-8") 或分开设置java<br/>resp.setContentType("text/html;charset=UTF-8");<br/>// 或<br/>resp.setCharacterEncoding("UTF-8");<br/>resp.setHeader("Content-Type", "text/html");<br/>必须在 getWriter() 前设置;否则浏览器按默认编码(如 ISO-8859-1)解析。
统一编码 Filter创建全局字符编码过滤器实现 Filter,在 doFilter 中统一设置请求/响应编码java<br/>public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {<br/> req.setCharacterEncoding("UTF-8");<br/> res.setCharacterEncoding("UTF-8");<br/> chain.doFilter(req, res);<br/>}<br/>最佳实践:通过 web.xml@WebFilter("/*") 注册,避免每个 Servlet 重复设置。

总结

  • POST 乱码 → req.setCharacterEncoding("UTF-8")
  • GET 乱码 → 配置 Tomcat URIEncoding="UTF-8"
  • 响应乱码 → resp.setContentType("...;charset=UTF-8")
  • 终极方案 → 使用全局 Filter 统一处理

第五章:Servlet 生命周期与线程模型

5.1 Servlet 的生命周期(init, service, destroy)

方法名称语法用途代码示例注意事项
initpublic void init(ServletConfig config) throws ServletException初始化 Servlet 实例,仅在生命周期开始时调用一次。用于加载配置、建立数据库连接等一次性操作。java<br/>@Override<br/>public void init(ServletConfig config) throws ServletException {<br/> String dbUrl = config.getInitParameter("dbUrl");<br/> try {<br/> Class.forName("com.mysql.cj.jdbc.Driver");<br/> connection = DriverManager.getConnection(dbUrl, "user", "pass");<br/> System.out.println("Database connected in init()");<br/> } catch (Exception e) {<br/> throw new ServletException("Failed to initialize DB", e);<br/> }<br/>}<br/>必须调用 super.init(config) 或重写无参 init() 方法以避免 config 丢失;若抛出 ServletException,容器将不启用该 Servlet。
servicepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException处理客户端请求的核心方法。每次 HTTP 请求都会触发,由容器分配新线程执行。// 通常不直接重写此方法
// HttpServlet 已实现自动分发至 doGet/doPost 等
开发者应继承 HttpServlet 并重写 doGet/doPost;直接重写需自行解析 HTTP 方法。
destroypublic void destroy()在 Servlet 被销毁前释放资源,仅调用一次。用于关闭连接、停止线程、持久化数据等。java<br/>@Override<br/>public void destroy() {<br/> if (connection != null) {<br/> try {<br/> connection.close();<br/> System.out.println("Database connection closed");<br/> } catch (SQLException e) {<br/> e.printStackTrace();<br/> }<br/> }<br/>}<br/>容器保证在所有请求处理完成后才调用;异常终止(如 kill -9)不会触发此方法。

💡 生命周期流程: 容器加载类 → 反射创建实例 → 调用 init() → 多次调用 service()(多线程)→ 应用关闭时调用 destroy() → 对象被 GC 回收。

5.2 单例模式与多线程安全问题

概念/问题说明风险示例安全实践注意事项
单例模式每个 Servlet 类在容器中仅有一个实例,所有请求共享该实例。java<br/>public class UnsafeServlet extends HttpServlet {<br/> private int counter = 0;<br/> protected void doGet(HttpServletRequest req, HttpServletResponse resp) {<br/> counter++; // 非线程安全<br/> resp.getWriter().println("Count: " + counter);<br/> }<br/>}<br/>使用局部变量、只读成员、同步块或线程安全集合。实例变量(成员变量)在多线程下存在竞态条件,应避免使用。
线程安全原则所有请求共用同一个 Servlet 实例,但每个请求拥有独立的 HttpServletRequestHttpServletResponse 对象。成员变量被多个线程同时修改导致数据错乱。- 业务逻辑使用方法内局部变量
- 共享资源使用 synchronizedjava.util.concurrent 工具
- 使用 ThreadLocal 存储线程私有数据
HttpServletRequest/HttpServletResponse 本身是线程安全的,因其为每个请求新建。
安全示例正确使用局部变量和外部服务java<br/>public class SafeServlet extends HttpServlet {<br/> private final UserService userService = new UserService();<br/> protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {<br/> String userId = req.getParameter("id");<br/> User user = userService.findById(userId); // 假设 userService 是无状态或线程安全的<br/> resp.getWriter().println("User: " + user.getName());<br/> }<br/>}<br/>将状态封装在无状态服务中;避免在 Servlet 中维护用户会话状态(应使用 HttpSession)。若必须使用可变成员,需加锁,但会降低并发性能。

5.3 异步处理支持(Servlet 3.0+)

方法/特性语法用途代码示例注意事项
启用异步支持@WebServlet(urlPatterns = "/async", asyncSupported = true)声明 Servlet 支持异步处理。@WebServlet(urlPatterns = "/long-task", asyncSupported = true)
public class AsyncServlet extends HttpServlet { ... }
必须显式设置 asyncSupported = true,否则调用 startAsync() 会抛 IllegalStateException
启动异步上下文AsyncContext startAsync(HttpServletRequest req, HttpServletResponse resp)暂挂当前请求处理,释放容器线程,后续在其他线程完成响应。java<br/>@Override<br/>protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {<br/> AsyncContext asyncCtx = req.startAsync();<br/> asyncCtx.setTimeout(10000); // 10秒超时<br/> executor.submit(() -> {<br/> try {<br/> // 模拟耗时操作<br/> Thread.sleep(5000);<br/> asyncCtx.getResponse().getWriter().println("Async task done");<br/> asyncCtx.complete();<br/> } catch (Exception e) {<br/> asyncCtx.complete();<br/> }<br/> });<br/>}<br/>必须调用 asyncCtx.complete() 结束响应;超时未完成会触发 timeout 事件。
设置超时回调void addTimeoutListener(Runnable listener)注册异步操作超时时的回调。asyncCtx.addTimeoutListener(() -> {
System.out.println("Async operation timed out");
});
超时后容器可能自动 complete,需在 listener 中清理资源。
分派回同步处理void dispatch(String path)将异步请求转发给另一个 Servlet 或 JSP 进行最终渲染。asyncCtx.dispatch("/result.jsp");常用于异步计算完成后跳转到结果页面。
获取原始请求/响应HttpServletRequest getRequest()
HttpServletResponse getResponse()
AsyncContext 中获取原始请求和响应对象。java<br/>HttpServletRequest req = asyncCtx.getRequest();<br/>String param = req.getParameter("data");<br/>可在异步线程中安全使用这些对象写入响应。

异步适用场景

  • 调用外部慢速服务(如 REST API、数据库查询)
  • 长轮询(Long Polling)或 Server-Sent Events(SSE)
  • 提高容器线程利用率,避免阻塞 I/O 占用工作线程

⚠️ 注意

  • 异步处理不能提升单个请求的速度,但能提升系统吞吐量。
  • 必须使用线程池(如 Executors.newCachedThreadPool())管理后台任务,避免创建过多线程。

第六章:Servlet 配置方式

6.1 使用 web.xml 进行传统配置

配置元素语法结构用途示例代码注意事项
<servlet> 声明xml<br/><servlet><br/> <servlet-name>名称</servlet-name><br/> <servlet-class>全限定类名</servlet-class><br/> <init-param><br/> <param-name>参数名</param-name><br/> <param-value>参数值</param-value><br/> </init-param><br/> <load-on-startup>整数</load-on-startup><br/></servlet><br/>注册 Servlet 类并设置初始化参数和加载顺序。xml<br/>HelloServlet<br/>com.example.HelloServlet<br/>greeting<br/>Welcome<br/>1<br/>servlet-name 必须唯一;load-on-startup 值越小优先级越高,负数或省略表示首次请求时加载。
<servlet-mapping> 映射xml<br/><servlet-mapping><br/> <servlet-name>已声明的名称</servlet-name><br/> <url-pattern>URL 模式</url-pattern><br/></servlet-mapping><br/>将 Servlet 绑定到一个或多个 URL 路径。xml<br/>HelloServlet<br/>/hello<br/>*.do<br/>支持多 <url-pattern>;模式类型包括:精确匹配(/hello)、路径匹配(/api/*)、扩展名匹配(*.do)。
<filter> 声明xml<br/><filter><br/> <filter-name>名称</filter-name><br/> <filter-class>全限定类名</filter-class><br/> <init-param>...</init-param><br/></filter><br/>注册 Filter 类。xml<br/>EncodingFilter<br/>com.example.EncodingFilter<br/>...<br/>与 Servlet 声明结构类似,但使用 <filter> 标签。
<filter-mapping> 映射xml<br/><filter-mapping><br/> <filter-name>名称</filter-name><br/> <url-pattern>/*</url-pattern><br/></filter-mapping><br/>指定 Filter 应用的 URL 范围。xml<br/>EncodingFilter<br/>/*<br/>可使用 <servlet-name> 替代 <url-pattern> 以仅过滤特定 Servlet。
<listener> 声明xml<br/><listener><br/> <listener-class>全限定监听器类名</listener-class><br/></listener><br/>注册监听器(如上下文、会话生命周期事件)。xml<br/>com.example.AppContextListener<br/>监听器类需实现 ServletContextListenerHttpSessionListener 等接口。
metadata-complete 属性<web-app metadata-complete="true">若设为 true,容器将忽略所有注解配置,仅使用 web.xmlxml<br/><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"<br/> version="4.0"<br/> metadata-complete="true"><br/>默认为 false;若使用注解,必须设为 false 或省略。

6.2 使用注解(@WebServlet, @WebFilter, @WebListener)

注解名称语法用途示例代码注意事项
@WebServletjava<br/>@WebServlet(<br/> name = "名称",<br/> urlPatterns = {"/path", "*.ext"},<br/> initParams = {<br/> @WebInitParam(name = "key", value = "val")<br/> },<br/> loadOnStartup = 1,<br/> asyncSupported = true<br/>)<br/>替代 web.xml 中的 <servlet><servlet-mapping>java<br/>@WebServlet(<br/> name = "HelloServlet",<br/> urlPatterns = "/hello",<br/> initParams = {<br/> @WebInitParam(name = "greeting", value = "Hi")<br/> },<br/> loadOnStartup = 2<br/>)<br/>public class HelloServlet extends HttpServlet { ... }<br/>valueurlPatterns 互斥;若未指定 name,默认为全类名;需确保容器扫描该类(Tomcat 自动扫描,Spring Boot 需 @ServletComponentScan)。
@WebFilterjava<br/>@WebFilter(<br/> filterName = "名称",<br/> urlPatterns = "/*",<br/> servletNames = {"Servlet1"},<br/> dispatcherTypes = {DispatcherType.REQUEST}<br/>)<br/>替代 web.xml 中的 <filter><filter-mapping>java<br/>@WebFilter(urlPatterns = "/*")<br/>public class EncodingFilter implements Filter {<br/> public void doFilter(...) { ... }<br/>}<br/>urlPatternsservletNamesvalue 可组合使用;dispatcherTypes 控制过滤的请求类型(REQUEST/FORWARD/ERROR 等)。
@WebListener@WebListener标记监听器类,自动注册。java<br/>@WebListener<br/>public class AppContextListener implements ServletContextListener {<br/> public void contextInitialized(ServletContextEvent sce) { ... }<br/>}<br/>无需参数;容器根据实现的接口自动识别监听事件类型。
启用注解扫描(Spring Boot)@ServletComponentScan(basePackages = "com.example")在 Spring Boot 中启用对 @WebServlet 等注解的扫描。java<br/>@SpringBootApplication<br/>@ServletComponentScan("com.example")<br/>public class Application {<br/> public static void main(String[] args) {<br/> SpringApplication.run(Application.class, args);<br/> }<br/>}<br/>非 Spring Boot 项目(如纯 Tomcat)无需此注解,容器自动处理。

6.3 动态注册 Servlet 与 Filter(编程式配置)

操作类型方法/接口语法用途示例代码注意事项
实现 ServletContainerInitializerpublic interface ServletContainerInitializer {
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
在应用启动时动态注册组件。java<br/>public class MyServletInitializer implements ServletContainerInitializer {<br/> public void onStartup(Set<Class<?>> c, ServletContext ctx) {<br/> ServletRegistration.Dynamic reg = ctx.addServlet("DynamicServlet", DynamicServlet.class);<br/> reg.addMapping("/dynamic");<br/> reg.setLoadOnStartup(1);<br/> }<br/>}<br/>需在 META-INF/services/jakarta.servlet.ServletContainerInitializer 文件中声明实现类全名。
使用 ServletContext.addServletServletRegistration.Dynamic addServlet(String name, Class<? extends Servlet> servletClass)动态添加 Servlet。java<br/>ServletContext ctx = ...;<br/>ServletRegistration.Dynamic reg = ctx.addServlet("ReportServlet", ReportServlet.class);<br/>reg.setAsyncSupported(true);<br/>reg.addMapping("/report/*");<br/>返回 ServletRegistration.Dynamic 对象,可进一步配置 init 参数、映射等。
使用 ServletContext.addFilterFilterRegistration.Dynamic addFilter(String name, Class<? extends Filter> filterClass)动态添加 Filter。java<br/>FilterRegistration.Dynamic freg = ctx.addFilter("AuthFilter", AuthFilter.class);<br/>freg.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/api/*");<br/>addMappingForUrlPatterns 支持通配符;第二个参数 isMatchAfter 控制过滤器链顺序。
在 Spring Boot 中使用 RegistrationBean@Bean通过 Spring Bean 方式注册。java<br/>@Bean<br/>public ServletRegistrationBean myServlet() {<br/> return new ServletRegistrationBean<>(new MyServlet(), "/custom/*");<br/>}<br/>@Bean<br/>public FilterRegistrationBean encodingFilter() {<br/> FilterRegistrationBean bean = new FilterRegistrationBean<>();<br/> bean.setFilter(new EncodingFilter());<br/> bean.addUrlPatterns("/*");<br/> bean.setOrder(1);<br/> return bean;<br/>}<br/>setOrder() 控制 Filter 执行顺序(数值越小越先执行);适用于需要依赖注入的场景。
获取并修改现有注册ServletRegistration getServletRegistration(String name)
FilterRegistration getFilterRegistration(String name)
查询已注册的组件并修改其配置。java<br/>ServletRegistration reg = ctx.getServletRegistration("default");<br/>if (reg != null) {<br/> reg.addMapping("/fallback/*");<br/>}<br/>通常用于扩展现有 Servlet(如 default Servlet)的功能。

配置方式对比

  • web.xml:集中管理,适合大型团队或遗留系统。
  • 注解:简洁直观,适合现代轻量级开发。
  • 编程式:高度灵活,适用于框架集成、条件注册或运行时动态配置。

第七章:高级特性

7.1 过滤器(Filter)机制

方法/概念语法用途代码示例注意事项
doFilterpublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException拦截请求和响应,执行预处理或后处理逻辑。java<br/>public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {<br/> HttpServletRequest request = (HttpServletRequest) req;<br/> System.out.println("Before processing: " + request.getRequestURI());<br/> chain.doFilter(req, res); // 放行<br/> System.out.println("After processing");<br/>}<br/>必须调用 chain.doFilter() 否则请求中断;可修改请求/响应对象(如包装)。
initpublic void init(FilterConfig filterConfig) throws ServletException初始化 Filter,获取配置参数。java<br/>public void init(FilterConfig config) throws ServletException {<br/> this.encoding = config.getInitParameter("encoding");<br/>}<br/>仅调用一次;可通过 filterConfig.getServletContext() 获取上下文。
destroypublic void destroy()释放 Filter 资源。public void destroy() { System.out.println("Filter destroyed"); }应用关闭时调用一次。
FilterChainvoid doFilter(ServletRequest request, ServletResponse response)将请求传递给下一个 Filter 或目标 Servlet。chain.doFilter(request, response);若不调用,后续组件不会执行;可在放行前后添加逻辑。
拦截路径匹配urlPatterns 支持:
- 精确路径 /login
- 目录匹配 /api/*
- 扩展名匹配 *.jsp
- 全局匹配 /*
控制 Filter 应用范围。@WebFilter(urlPatterns = "/admin/*")
public class AuthFilter implements Filter { ... }
/* 匹配所有请求(包括静态资源);/ 不是有效模式。
过滤器链顺序web.xml<filter-mapping> 的声明顺序决定执行顺序;注解方式按类名字母排序(不可靠)。控制多个 Filter 的执行次序。web.xml 中先声明 A 再声明 B,则 A 先执行(请求阶段),B 先执行(响应阶段)。响应阶段的执行顺序与请求阶段相反。

7.2 监听器(Listener)机制

监听器接口回调方法触发时机示例代码注意事项
ServletContextListenercontextInitialized(ServletContextEvent sce)
contextDestroyed(ServletContextEvent sce)
Web 应用启动/关闭时触发。java<br/>public void contextInitialized(ServletContextEvent sce) {<br/> System.out.println("App started");<br/> sce.getServletContext().setAttribute("startTime", System.currentTimeMillis());<br/>}<br/>用于初始化全局资源(如数据库连接池、缓存)。
HttpSessionListenersessionCreated(HttpSessionEvent se)
sessionDestroyed(HttpSessionEvent se)
会话创建/销毁时触发。java<br/>public void sessionCreated(HttpSessionEvent se) {<br/> System.out.println("New session: " + se.getSession().getId());<br/>}<br/>可用于统计在线用户数。
HttpSessionAttributeListenerattributeAdded(HttpSessionBindingEvent e)
attributeRemoved(...)
attributeReplaced(...)
会话属性变更时触发。java<br/>public void attributeAdded(HttpSessionBindingEvent e) {<br/> if ("user".equals(e.getName())) {<br/> System.out.println("User logged in: " + e.getValue());<br/> }<br/>}<br/>可监控登录状态变化。
ServletRequestListenerrequestInitialized(ServletRequestEvent sre)
requestDestroyed(ServletRequestEvent sre)
每个 HTTP 请求开始/结束时触发。java<br/>public void requestInitialized(ServletRequestEvent sre) {<br/> HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();<br/> System.out.println("Request from: " + req.getRemoteAddr());<br/>}<br/>可用于请求日志、性能监控。
注册方式@WebListenerweb.xml <listener>声明监听器生效。java<br/>@WebListener<br/>public class AppListener implements ServletContextListener { ... }<br/>容器自动识别实现的接口类型。

7.3 会话管理(HttpSession)

方法语法用途代码示例注意事项
getSessionHttpSession getSession()
HttpSession getSession(boolean create)
获取当前会话,若不存在则创建(或根据参数决定)。java<br/>HttpSession session = req.getSession();<br/>session.setAttribute("user", currentUser);<br/>默认 create=truegetSession(false) 用于检查是否已登录。
setAttributevoid setAttribute(String name, Object value)存储会话属性。session.setAttribute("cart", new ShoppingCart());值必须可序列化(若需集群复制)。
getAttributeObject getAttribute(String name)获取会话属性。User user = (User) session.getAttribute("user");返回 Object,需强制类型转换。
removeAttributevoid removeAttribute(String name)移除指定属性。session.removeAttribute("tempData");不会销毁整个会话。
invalidatevoid invalidate()立即销毁会话及所有属性。session.invalidate(); // 用户登出会话 ID 失效,客户端下次请求将创建新会话。
setMaxInactiveIntervalvoid setMaxInactiveInterval(int interval)设置会话超时时间(秒)。session.setMaxInactiveInterval(1800); // 30分钟覆盖 web.xml 中的全局设置;0 或负数表示永不过期(不推荐)。
getIdString getId()获取会话唯一标识(JSESSIONID)。String id = session.getId();用于调试或日志追踪。
isNewboolean isNew()判断会话是否为本次请求新建。java<br/>if (session.isNew()) {<br/> resp.getWriter().println("Welcome first-time visitor!");<br/>}<br/>首次访问且无 JSESSIONID Cookie 时返回 true

💡 会话存储机制

  • 服务端:内存中存储 HttpSession 对象
  • 客户端:通过 Cookie(JSESSIONID=xxx)或 URL 重写传递 ID
  • 安全建议:生产环境启用 HTTPS,防止会话劫持

7.4 文件上传与下载处理

功能方法/配置语法用途代码示例注意事项
启用上传支持@MultipartConfigweb.xml <multipart-config>java<br/>@MultipartConfig(<br/> fileSizeThreshold = 1024 * 1024,<br/> maxFileSize = 1024 * 1024 * 5,<br/> maxRequestSize = 1024 * 1024 * 10<br/>)<br/>声明 Servlet 支持 multipart/form-data 请求。java<br/>@WebServlet("/upload")<br/>@MultipartConfig(maxFileSize = 5_000_000)<br/>public class UploadServlet extends HttpServlet { ... }<br/>必须添加,否则 getParts() 抛异常。
获取上传文件Part getPart(String name)Part filePart = req.getPart("file");获取单个上传文件部件。java<br/>Part filePart = req.getPart("avatar");<br/>String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();<br/>filePart.write("/uploads/" + fileName);<br/>getSubmittedFileName() 可能为 null(需校验);write() 保存到磁盘。
获取所有部件Collection<Part> getParts()Collection<Part> parts = req.getParts();遍历所有表单字段和文件。java<br/>for (Part part : req.getParts()) {<br/> if (part.getContentType() != null) {<br/> // 是文件<br/> } else {<br/> // 是普通参数<br/> }<br/>}<br/>普通参数可通过 part.getInputStream() 读取值。
文件下载响应设置响应头 + 输出流resp.setContentType("application/octet-stream");
resp.setHeader("Content-Disposition", "attachment; filename=report.pdf");
强制浏览器下载文件而非打开。java<br/>resp.setHeader("Content-Length", String.valueOf(file.length()));<br/>try (InputStream in = Files.newInputStream(path)) {<br/> in.transferTo(resp.getOutputStream());<br/>}<br/>必须设置 Content-Disposition: attachment;中文文件名需 URL 编码(如 URLEncoder.encode(name, "UTF-8"))。
流式下载大文件使用缓冲区避免内存溢出byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
安全传输大文件。java<br/>try (InputStream in = new FileInputStream(file);<br/> OutputStream out = resp.getOutputStream()) {<br/> byte[] buf = new byte[8192];<br/> int n;<br/> while ((n = in.read(buf)) >= 0) {<br/> out.write(buf, 0, n);<br/> }<br/>}<br/>避免一次性加载整个文件到内存。

安全建议

  • 上传文件需校验扩展名、MIME 类型、内容特征(防木马)
  • 下载路径需校验,防止目录遍历(如 ../../../etc/passwd

第八章:Jakarta Servlet 新特性(6.0+)

8.1 包名变更:从 javax.servletjakarta.servlet

变更项说明迁移操作示例(旧 → 新)注意事项
命名空间因 Oracle 将 Java EE 移交 Eclipse 基金会,所有 API 包名从 javax.* 改为 jakarta.*1. 全局替换代码中 import javax.servletimport jakarta.servlet
2. 更新 Maven/Gradle 依赖
3. 修改 web.xml 命名空间
import javax.servlet.http.HttpServlet;

import jakarta.servlet.http.HttpServlet;
不兼容:javax.servlet.Servletjakarta.servlet.Servlet 是两个不同类,混用会导致 ClassCastExceptionClassNotFoundException
Maven 依赖Jakarta EE 9+ 使用新坐标。替换依赖:
旧坐标新坐标
groupId: javax.servletgroupId: jakarta.servlet
artifactId: javax.servlet-apiartifactId: jakarta.servlet-api
version: 4.0.1version: 6.0.0
scope: providedscope: provided
版本对应关系:
- Servlet 4.0 → javax(Java EE 8)
- Servlet 5.0 → jakarta(Jakarta EE 9,仅包名变)
- Servlet 6.0 → jakarta(Jakarta EE 10,含新功能)
web.xml 命名空间XML Schema URI 更新。修改根元素:xml<br/>旧:<br/><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"<br/> version="4.0"><br/><br/>新:<br/><web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"<br/> version="6.0"><br/>version 需匹配 Servlet 规范版本(如 6.0 对应 Jakarta EE 10)。
容器兼容性Tomcat 10+、Jetty 11+、Undertow 2.2+ 支持 jakarta.*升级 Web 容器:
- Tomcat 9 → 仅支持 javax
- Tomcat 10.0.x → Jakarta EE 9(Servlet 5.0)
- Tomcat 10.1.x → Jakarta EE 10(Servlet 6.0)
若使用 Spring Boot:
- Spring Boot 2.x + Tomcat 9 → javax
- Spring Boot 3.x + Tomcat 10.1 → jakarta
不可混用:项目中所有依赖(包括第三方库如 FileUpload)必须统一为 jakartajavax

8.2 HTTP/2 支持与 Server Push(已弃用)

特性说明状态替代方案注意事项
HTTP/2 原生支持Servlet 4.0(Java EE 8)首次引入对 HTTP/2 的容器级支持(多路复用、头部压缩等)。保留:Jakarta Servlet 6.0+ 仍支持 HTTP/2,但由容器实现(如 Tomcat 10 启用 SSL 后自动协商)。无需代码修改;确保服务器配置 TLS 1.2+ 并启用 HTTP/2。应用层无需感知;可通过 HttpServletRequest.getProtocol() 查看协议(如 “HTTP/2.0”)。
Server Push通过 PushBuilder 主动推送资源(如 CSS/JS),减少往返延迟。已弃用:Servlet 6.0 起标记为 deprecated,Jakarta EE 11(Servlet 6.1)中移除。- 使用 <link rel="preload">
- 利用 HTTP/1.1 并行请求
- 采用 Early Hints (103) 响应
不再推荐使用;现代浏览器对 Server Push 支持差,易导致资源重复加载。
PushBuilder 用法(历史参考)HttpServletRequest.getPushBuilder() 返回构建器。仅适用于 Servlet 4.0–5.0java<br/>// Servlet 5.0 示例(已过时)<br/>PushBuilder push = req.getPushBuilder();<br/>if (push != null) {<br/> push.path("/style.css").push();<br/>}<br/>在 Servlet 6.0+ 中调用 getPushBuilder() 返回 null

8.3 Servlet 6.1 新增功能

新增功能方法/语法用途代码示例注意事项
带状态码的重定向void sendRedirect(int status, String location)允许指定 3xx 状态码(如 301、303),替代默认 302。java<br/>// 永久重定向<br/>resp.sendRedirect(301, "/new-page");<br/><br/>// See Other<br/>resp.sendRedirect(303, "/confirmation");<br/>支持的状态码必须是 3xx 重定向类;否则抛 IllegalArgumentException
错误分派中的查询字符串RequestDispatcher.ERROR_QUERY_STRING在错误页面中获取原始请求的查询参数。java<br/>// error.jsp 或 ErrorServlet 中<br/>String qs = (String) request.getAttribute(RequestDispatcher.ERROR_QUERY_STRING);<br/>System.out.println("Original query: " + qs);<br/>属性名为 "jakarta.servlet.error.query_string";仅在 ERROR 分派时存在。
ByteBuffer I/O 支持int read(ByteBuffer buffer)
void write(ByteBuffer buffer)
ServletInputStream/ServletOutputStream 中直接使用 NIO ByteBuffer,提升 I/O 性能。java<br/>ServletInputStream in = req.getInputStream();<br/>ByteBuffer buf = ByteBuffer.allocate(1024);<br/>int n = in.read(buf);<br/><br/>ServletOutputStream out = resp.getOutputStream();<br/>out.write(buf);<br/>适用于高并发、非阻塞 I/O 场景;可与虚拟线程(Java 21+)协同优化吞吐量。
Charset 编码设置void setCharacterEncoding(Charset charset)使用 java.nio.charset.Charset 对象替代字符串编码名,避免拼写错误。java<br/>// 推荐方式<br/>resp.setCharacterEncoding(StandardCharsets.UTF_8);<br/><br/>// 旧方式(仍支持)<br/>resp.setCharacterEncoding("UTF-8");<br/>提供编译期类型安全;StandardCharsets 包含常用编码(UTF_8ISO_8859_1 等)。
最低 Java 版本要求Jakarta EE 11(含 Servlet 6.1)要求 Java 17+利用现代 Java 特性(Records、Pattern Matching 等)。可在 Servlet 中安全使用 switch 表达式、var 等。无法在 Java 8/11 上运行 Jakarta EE 11 应用。

Servlet 6.1 核心价值

  • 精细化控制:如精确重定向状态码、完整错误上下文
  • 现代化 I/O:ByteBuffer 支持为高性能网关、代理提供基础
  • 类型安全:Charset 参数消除字符串硬编码风险
  • 云原生就绪:与 Java 17+、虚拟线程、容器化部署深度协同

第九章:部署与调试

9.1 打包为 WAR 文件

步骤名称操作细节工具/命令注意事项
确保项目结构合规项目必须包含 WEB-INF/web.xml(可选)和 WEB-INF/classes/ 目录存放编译后的类文件。Maven 项目默认结构为:
- src/main/java → 编译到 WEB-INF/classes
- src/main/webapp → 根目录静态资源
使用 Maven Archetype:
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp
若使用注解配置(如 @WebServlet),web.xml 可省略,但需确保 metadata-complete="false"(默认)。
设置打包类型pom.xml 中指定 <packaging>war</packaging><project>...<packaging>war</packaging>...</project>默认为 jar;若未设置,Maven 不会生成 WAR。
添加 Servlet API 依赖引入 jakarta.servlet-api,作用域为 providedxml<br/><dependency><br/> <groupId>jakarta.servlet</groupId><br/> <artifactId>jakarta.servlet-api</artifactId><br/> <version>6.0.0</version><br/> <scope>provided</scope><br/></dependency><br/>scope=provided 表示容器已提供实现,避免打包进 WAR 导致冲突。
执行打包命令使用 Maven 构建mvn clean package成功后在 target/ 目录生成 项目名.war 文件;大小通常几 MB 到几十 MB。
验证 WAR 内容解压 WAR 文件检查结构jar -tf target/myapp.war
或用解压软件打开
必须包含:
- WEB-INF/classes/com/example/HelloServlet.class
- META-INF/MANIFEST.MF
- 静态资源(如 index.html)位于根目录

9.2 部署到 Tomcat / Jetty

容器部署方式操作步骤访问路径注意事项
Apache Tomcat手动拷贝 WAR1. 启动 Tomcat(bin/startup.sh.bat
2. 将 WAR 文件复制到 webapps/ 目录
3. Tomcat 自动解压并部署
http://localhost:8080/项目名/路径
(如 http://localhost:8080/jakarta-demo/hello
- WAR 文件名即上下文路径
- 若命名为 ROOT.war,则路径为 /
- 查看日志:logs/localhost.<date>.log
Apache TomcatContext 配置(推荐生产)1. 在 conf/Catalina/localhost/ 下创建 myapp.xml
2. 内容:<Context docBase="/path/to/app" />
http://localhost:8080/myapp/...- docBase 可指向未打包的目录或 WAR 文件
- 文件名决定上下文路径
- 不修改 server.xml,更安全
Eclipse Jetty命令行部署1. 将 WAR 放入 Jetty 的 webapps/ 目录
2. 启动 Jetty:java -jar start.jar
http://localhost:8080/项目名/路径Jetty 默认端口 8080;可通过 jetty.port=8081 修改
Eclipse Jetty嵌入式部署(开发)在 Java 代码中启动:
java<br/>Server server = new Server(8080);<br/>WebAppContext webapp = new WebAppContext();<br/>webapp.setWar("target/myapp.war");<br/>server.setHandler(webapp);<br/>server.start();<br/>
同上适用于单元测试或快速原型验证
通用验证检查部署状态- Tomcat Manager 页面(需配置用户)
- 查看 webapps/ 下是否生成同名文件夹
若部署成功,webapps/ 下应有 myapp/ 目录首次部署可能较慢(因解压和初始化)

9.3 常见错误排查(404、500、版本不兼容等)

错误现象可能原因排查步骤解决方案
HTTP 404 Not Found1. 上下文路径错误
2. Servlet 映射未生效
3. WAR 未正确部署
1. 检查 URL 是否为 http://host:port/项目名/servlet路径
2. 查看 webapps/ 下是否有解压目录
3. 检查 web.xml 或注解中的 urlPatterns
- 确认 WAR 文件名(如 demo.war/demo
- 若使用注解,确保类被扫描(非 Spring Boot 需在 web.xmlmetadata-complete="false"
HTTP 500 Internal Error1. Servlet 类找不到
2. 初始化异常
3. 代码逻辑错误
1. 查看容器日志(Tomcat: logs/catalina.out
2. 搜索 ClassNotFoundExceptionServletException
- 确认依赖未打包进 WAR(如 Servlet API 应为 provided
- 检查 init() 中是否有异常
- 修复业务代码中的空指针等错误
ClassNotFoundException: javax.servlet.Servlet混用 javaxjakarta1. 检查代码 import
2. 检查依赖坐标
3. 检查容器版本
- 代码中统一使用 jakarta.servlet.*
- Maven 依赖使用 jakarta.servlet:jakarta.servlet-api
- 使用 Tomcat 10+(非 Tomcat 9)
Servlet not initializedload-on-startup 配置错误或 init 抛异常查看日志中是否有 StandardWrapper.Throwable- 确保 init() 不抛未处理异常
- 检查 web.xmlload-on-startup 值是否合理
中文乱码请求/响应编码未设置1. POST 提交乱码 → 未设 req.setCharacterEncoding("UTF-8")
2. GET 乱码 → Tomcat 未设 URIEncoding="UTF-8"
3. 响应乱码 → 未设 resp.setContentType("...;charset=UTF-8")
- 添加全局 Filter 统一设置编码
- 修改 Tomcat conf/server.xml<Connector ... URIEncoding="UTF-8" />
端口冲突8080 被占用执行 `netstat -anofindstr :8080(Windows)或 lsof -i :8080`(Linux)
WAR 部署失败1. 文件损坏
2. 权限不足
3. 磁盘空间不足
1. 检查日志中 SEVERE 级别错误
2. 尝试手动解压 WAR 验证完整性
- 重新执行 mvn package
- 确保 Tomcat 对 webapps/ 有写权限
- 清理磁盘空间

调试最佳实践

  • 始终查看容器日志(catalina.outjetty.log
  • 开发阶段使用 IDE 集成部署(如 IntelliJ + Tomcat 插件),支持热加载
  • 生产环境采用 context.xml 方式部署,避免直接操作 webapps/

第十章:最佳实践与生态整合

10.1 与 JSP、EL、JPA 等 Jakarta EE 组件集成

组件集成方式用途示例代码 / 配置注意事项
JSP(Jakarta Server Pages)在 Servlet 中通过 RequestDispatcher 转发到 JSP 页面实现 MVC 模式:Servlet 处理逻辑,JSP 负责视图渲染java<br/>protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {<br/> req.setAttribute("message", "Hello from Servlet");<br/> req.getRequestDispatcher("/result.jsp").forward(req, resp);<br/>}<br/>- JSP 文件放在 src/main/webapp/
- 避免在 JSP 中写 Java 脚本(使用 EL + JSTL)
EL(Expression Language)在 JSP 中使用 ${} 表达式访问请求/会话属性简化数据输出,替代 <%= %> 脚本<p>Welcome, ${user.name}!</p>
<c:if test="${not empty cart.items}">...</c:if>
需引入 JSTL:
xml<br/><dependency><br/> <groupId>org.glassfish.web</groupId><br/> <artifactId>jakarta.servlet.jsp.jstl</artifactId><br/></dependency><br/>
JPA(Jakarta Persistence API)在 Servlet 中注入或创建 EntityManager 操作数据库实现数据持久化,替代 JDBC 手动编码java<br/>@WebServlet("/users")<br/>public class UserServlet extends HttpServlet {<br/> protected void doGet(...) {<br/> EntityManagerFactory emf = ...; // 通常由 CDI 或手动管理<br/> EntityManager em = emf.createEntityManager();<br/> List<User> users = em.createQuery("SELECT u FROM User u", User.class).getResultList();<br/> req.setAttribute("users", users);<br/> req.getRequestDispatcher("/list.jsp").forward(req, resp);<br/> }<br/>}<br/>- 纯 Servlet 项目需手动管理 EntityManager 生命周期
- 推荐结合 CDI(如 Weld)实现依赖注入
- 生产环境应使用连接池(如 HikariCP)
CDI(Contexts and Dependency Injection)使用 @Inject 注入 Bean 到 Servlet实现控制反转,解耦业务逻辑java<br/>@WebServlet("/api")<br/>public class ApiService extends HttpServlet {<br/> @Inject<br/> private UserService userService;<br/><br/> protected void doGet(...) {<br/> User u = userService.findById("123");<br/> }<br/>}<br/>- 需添加 beans.xml(空文件即可)到 WEB-INF/
- 依赖 Jakarta EE 容器(如 WildFly、Payara)或嵌入 Weld
统一异常处理使用 web.xml<error-page> 或全局 Filter集中处理 404、500 等错误xml<br/><error-page><br/> <error-code>500</error-code><br/> <location>/error.jsp</location><br/></error-page><br/>错误页面可通过 request.getAttribute("jakarta.servlet.error.exception") 获取异常对象

10.2 与 Spring Boot / Spring MVC 的关系

对比维度Jakarta Servlet 原生Spring Boot / Spring MVC说明
编程模型继承 HttpServlet,重写 doGet/doPost使用 @RestController + @GetMapping 等注解Spring 提供更简洁的声明式 API
依赖注入需 Jakarta EE 容器 + CDI(如 @Inject内置 IoC 容器,支持 @Autowired@ServiceSpring 的 DI 更成熟、灵活
配置方式web.xml@WebServlet、动态注册application.properties + 注解自动配置Spring Boot “约定优于配置”大幅减少样板代码
JPA 集成手动管理 EntityManager 或依赖 CDISpring Data JPA:继承 JpaRepository 即可获得 CRUD 方法Spring Data JPA 极大简化数据访问层
内嵌服务器需外部部署(Tomcat/Jetty)内置 Tomcat/Jetty/Undertow,java -jar app.jar 直接运行Spring Boot 应用是可执行 JAR,非 WAR
适用场景轻量级 Web 应用、学习底层原理、受限环境企业级应用、快速开发、微服务Spring MVC 底层仍基于 Servlet(DispatcherServlet 是核心)
迁移路径可逐步引入 Spring:将 Servlet 逻辑移至 @Controller不建议从 Spring 回退到原生 Servlet若已使用 Spring Boot,无需直接编写 HttpServlet

💡 关键结论

  • Spring MVC 是 Servlet 的高级封装,其 DispatcherServlet 作为前端控制器,将请求路由到 @Controller 方法。
  • 学习原生 Servlet 有助于理解 Spring 底层机制(如请求生命周期、Filter 链)。
  • 新项目推荐 Spring Boot;遗留系统维护或教学场景可使用原生 Servlet。

10.3 微服务与云原生下的 Servlet 应用

场景挑战解决方案工具/框架注意事项
单体架构 → 微服务Servlet 应用通常为单体,难以拆分1. 将业务模块重构为独立服务
2. 使用 REST API 通信(Servlet 作为 API 网关或后端服务)
- Spring Boot + Spring Cloud
- Quarkus(轻量级 Jakarta EE)
避免在 Servlet 中直接调用其他服务数据库;采用 API 合约
容器化部署WAR 部署模式不适合 Docker1. 改造为可执行 JAR(如 Spring Boot)
2. 或使用 Tomcat 官方镜像部署 WAR
Dockerfile 示例:
dockerfile<br/>FROM tomcat:10.1-jre17<br/>COPY myapp.war /usr/local/tomcat/webapps/<br/>EXPOSE 8080<br/>
- 镜像大小:Tomcat 基础镜像约 200–300MB
- 推荐多阶段构建减小体积
无状态化HttpSession 依赖内存存储,不适用于水平扩展1. 禁用会话(RESTful 无状态)
2. 或将会话外置到 Redis
使用 Spring Session + Redis:
@EnableRedisHttpSession
原生 Servlet 可通过自定义 HttpSession 存储实现,但复杂度高
健康检查与可观测性缺乏标准监控端点1. 添加 /health/metrics 端点
2. 集成 Micrometer + Prometheus
在 Servlet 中实现:
java<br/>@WebServlet("/health")<br/>public class HealthServlet extends HttpServlet {<br/> protected void doGet(...) {<br/> resp.getWriter().print("{\"status\":\"UP\"}");<br/> }<br/>}<br/>
云原生平台(K8s)依赖 /health 进行存活/就绪探针
启动速度与内存占用传统 Servlet 容器启动慢、内存大采用 GraalVM 原生镜像或轻量级运行时- Quarkus(支持 Jakarta Servlet)
- Helidon
- Spring Native(实验性)
Tomcat + WAR 启动通常需数秒;Quarkus 可达毫秒级
服务发现与配置中心静态配置无法适应动态环境外部化配置 + 服务注册- Spring Cloud Config
- Consul/Etcd + 自定义初始化 Listener
可在 ServletContextListener.contextInitialized() 中加载远程配置

云原生最佳实践

  • 优先选择 JAR 而非 WAR:便于容器化、CI/CD 流水线
  • 无状态设计:避免 HttpSession,使用 JWT 或 OAuth2 令牌
  • 标准化运维接口:提供 /health/info/metrics
  • 轻量化运行时:考虑 Quarkus 或 Spring Boot AOT 编译