Servlet 全面解析(JavaWeb 核心)
一、介绍
Servlet(Server Applet)是 JavaWeb 的核心组件,本质是运行在服务器端的 Java 类,用于接收和处理客户端(浏览器)的 HTTP 请求,生成并返回响应(HTML、JSON、重定向等)。它是连接 Java 后端与前端的桥梁,也是 SpringMVC 等框架的底层基础。
特性:
- 运行环境 :依赖 Web 服务器(Tomcat、Jetty 等),由服务器实例化和管理(而非手动
new)。 - 遵循协议:基于 HTTP 协议,处理 GET/POST/PUT/DELETE 等请求方法。
- 生命周期:由服务器控制,从创建到销毁全程自动化。
- 线程模型:默认多线程(每个请求对应一个线程),需注意线程安全问题。
二、Servlet 生命周期(核心)
Servlet 的生命周期完全由 Web 服务器(如 Tomcat)管理,分为 4 个阶段:
| 阶段 | 触发时机 | 核心方法 | 说明 |
|---|---|---|---|
| 初始化(初始化一次) | 1. 服务器启动时;2. 首次访问 Servlet 时 | init(ServletConfig) |
只执行一次,用于初始化资源(如加载配置、连接数据库) |
| 处理请求(多次) | 每次客户端发送请求时 | service(),doGet()/doPost() |
service() 自动判断请求方法,调用对应 doXxx();可重写 doGet/doPost 处理具体逻辑 |
| 销毁(销毁一次) | 服务器关闭 / 项目卸载时 | destroy() |
只执行一次,用于释放资源(如关闭数据库连接、销毁线程) |
| 获取配置信息 | 任意阶段 | getServletConfig() |
获取 Servlet 配置参数(如初始化参数、上下文对象) |
生命周期示例代码:
java
@WebServlet("/lifeCycle")
public class LifeCycleServlet extends HttpServlet {
// 1. 初始化:仅执行一次
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("Servlet 初始化,加载资源...");
}
// 2. 处理 GET 请求(每次请求执行)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("处理 GET 请求...");
System.out.println("处理一次 GET 请求");
}
// 3. 销毁:仅执行一次
@Override
public void destroy() {
super.destroy();
System.out.println("Servlet 销毁,释放资源...");
}
}
Servlet 生命周期流程图:
否
是
GET
POST
PUT/DELETE
否
是
Web服务器启动/首次访问Servlet
是否已创建Servlet实例?
创建Servlet实例
调用init初始化(仅执行一次)
接收客户端HTTP请求
调用service()方法,自动判断请求类型
请求方法
调用doGet()
调用doPost()
调用对应doXxx()
生成响应并返回客户端
服务器是否关闭/项目卸载?
调用destroy()销毁(仅执行一次)
释放资源(数据库连接/线程等)
Servlet实例被GC回收
三、Servlet 核心 API 与关键对象
1. 核心父类 / 接口
Servlet:顶级接口,定义生命周期方法(init()/service()/destroy())。GenericServlet:实现Servlet接口,封装通用逻辑(如配置参数),抽象service()方法。HttpServlet:继承GenericServlet,专门处理 HTTP 请求,实现service()方法并拆分出doGet()/doPost()等方法(开发中直接继承此类)。
2. 三大核心对象(请求 / 响应 / 上下文)
| 对象 | 作用 | 核心方法 |
|---|---|---|
HttpServletRequest |
封装客户端请求信息(请求行、请求头、请求参数) | getParameter(String name):获取请求参数 getSession():获取 Session getHeader(String name):获取请求头 |
HttpServletResponse |
封装服务器响应信息(响应行、响应头、响应体) | getWriter():获取字符输出流(返回文本) setContentType():设置响应类型(如 JSON/HTML) sendRedirect():重定向 |
ServletContext |
全局上下文对象(每个 Web 应用唯一),用于共享数据、获取全局配置 | setAttribute(String key, Object value):设置全局属性 getAttribute(String key):获取全局属性 getRealPath(String path):获取文件绝对路径 |
Servlet 核心对象关系图
提供上下文
封装
封装
关联
输出
初始化参数
发送HTTP请求
返回响应
ServletContext 全局上下文对象(应用唯一)
HttpServlet 核心实现类
HttpServletRequest 请求对象(封装请求数据)
HttpServletResponse 响应对象(封装响应数据)
HttpSession 会话对象
客户端浏览器
ServletConfig 配置对象
区别:
| 维度 | HttpServletRequest |
HttpServletResponse |
|---|---|---|
| 作用 | 封装客户端请求数据 | 封装服务器响应数据 |
| 数据流向 | 客户端 → 服务器 | 服务器 → 客户端 |
| 核心操作 | 读取(获取参数、请求头) | 写入(输出内容、设置响应头) |
| 跳转方式 | 请求转发(服务器内部,地址栏不变) | 重定向(客户端跳转,地址栏变化) |
示例(获取参数 + 返回 JSON):
java
@WebServlet("/user")
public class UserServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 处理请求参数(解决中文乱码)
req.setCharacterEncoding("UTF-8");
String username = req.getParameter("username");
String age = req.getParameter("age");
// 2. 设置响应(返回 JSON)
resp.setContentType("application/json;charset=UTF-8");
String json = "{\"username\":\"" + username + "\",\"age\":\"" + age + "\"}";
resp.getWriter().write(json);
// 3. 操作 ServletContext(全局共享数据)
ServletContext context = getServletContext();
context.setAttribute("onlineCount", 100); // 设置全局属性
System.out.println("当前在线人数:" + context.getAttribute("onlineCount"));
}
}
Servlet 处理请求时序图:
ServletContext ServletConfig 自定义Servlet Web服务器(Tomcat) 客户端浏览器 ServletContext ServletConfig 自定义Servlet Web服务器(Tomcat) 客户端浏览器 alt [实例不存在] alt [GET请求] [POST请求- ] alt [服务器关闭] 发送HTTP请求(GET/POST) 检查实例是否存在 创建Servlet实例 获取初始化参数 传递参数 调用init()初始化 调用service()方法 判断请求方法(GET/POST) 调用doGet() 调用doPost() 获取全局上下文数据 生成响应数据(HTML/JSON) 返回HTTP响应 调用destroy()销毁 释放全局资源
四、Servlet 配置方式
Servlet 有两种配置方式:注解配置(主流) 和 XML 配置(传统)。
1. 注解配置(Servlet 3.0+ 支持,推荐)
通过 @WebServlet 注解直接标注类,无需修改 web.xml:
java
// 核心属性:urlPatterns(访问路径)、loadOnStartup(启动时加载)
@WebServlet(
urlPatterns = {"/hello", "/hi"}, // 多个访问路径
loadOnStartup = 1, // 服务器启动时初始化(默认-1:首次访问初始化)
initParams = {@WebInitParam(name = "encoding", value = "UTF-8")} // 初始化参数
)
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取初始化参数
String encoding = getServletConfig().getInitParameter("encoding");
resp.setContentType("text/html;charset=" + encoding);
resp.getWriter().write("Hello Servlet!");
}
}
2. XML 配置(web.xml,传统方式)
在 WEB-INF/web.xml 中配置 Servlet:
xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0">
<!-- 1. 注册 Servlet -->
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
<!-- 初始化参数 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 启动时加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 2. 映射访问路径 -->
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
五、Servlet 常见应用场景
- 处理表单提交:接收前端表单的用户名、密码,完成登录 / 注册逻辑。
- 生成动态页面:根据数据库数据动态生成 HTML 页面(如商品列表)。
- 提供接口:返回 JSON 数据,供前端 AJAX 调用(RESTful 接口)。
- 文件上传 / 下载 :处理客户端文件上传(需配合
Part接口)、返回文件下载响应。 - 权限拦截:结合 Filter 实现登录校验(如未登录则重定向到登录页)。
文件下载示例:
java
@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 设置响应头(告诉浏览器下载文件)
resp.setHeader("Content-Disposition", "attachment;filename=test.txt");
// 2. 读取服务器文件并写入响应流
String realPath = getServletContext().getRealPath("/files/test.txt");
FileInputStream fis = new FileInputStream(realPath);
ServletOutputStream os = resp.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
fis.close();
os.close();
}
}
六、Servlet 线程安全问题
问题根源:Servlet 是单例(服务器只创建一个实例),多请求共用同一个实例,若实例变量被修改会导致线程安全问题。
解决方案:
- 避免使用实例变量(改用局部变量,每个线程独立);
- 若必须使用实例变量,加锁(
synchronized),但会降低并发性能; - 使用 ThreadLocal 存储线程私有数据。
错误示例(线程不安全):
java
@WebServlet("/unsafe")
public class UnsafeServlet extends HttpServlet {
private int count = 0; // 实例变量:多线程共享,会被覆盖
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
count++; // 多请求同时修改,结果错误
resp.getWriter().write("count: " + count);
}
}
正确示例(局部变量):
java
@WebServlet("/safe")
public class SafeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int count = 0; // 局部变量:每个线程独立
count++;
resp.getWriter().write("count: " + count);
}
}
七、Servlet 与 JSP、SpringMVC 的关系
- Servlet vs JSP:
- JSP 本质是 Servlet(JSP 会被服务器编译为
.javaServlet 文件); - Servlet 适合纯逻辑处理,JSP 适合页面展示(已逐渐被前后端分离替代)。
- JSP 本质是 Servlet(JSP 会被服务器编译为
- Servlet vs SpringMVC:
- SpringMVC 是基于 Servlet 的封装,核心
DispatcherServlet本质是一个 Servlet; - SpringMVC 简化了请求映射、参数绑定、视图解析等,开发效率更高。
- SpringMVC 是基于 Servlet 的封装,核心
八、注意事项
- 掌握底层:Servlet 是 JavaWeb 底层,理解其生命周期和核心对象,能更好掌握 SpringMVC;
- 解决乱码 :GET 请求乱码(Tomcat 配置
URIEncoding="UTF-8"),POST 请求乱码(req.setCharacterEncoding("UTF-8")); - 路径问题 :访问路径分为绝对路径(
/hello)和相对路径(hello),推荐使用绝对路径; - 资源释放 :在
destroy()中释放数据库连接、线程池等资源,避免内存泄漏。
Servlet 是 JavaWeb 的基石,虽然现在企业开发更多使用 SpringMVC,但理解 Servlet 的核心原理,能帮助你解决框架封装后的底层问题,是后端工程师的必备基础。