Servlet 生命周期详解
一、Servlet 生命周期概述
Servlet 生命周期是 Java Web 开发的核心概念,指的是从 Servlet 被创建到被销毁的完整过程,由 Servlet 容器(如 Tomcat、Jetty 等)严格管理。理解 Servlet 生命周期对于开发高效、稳定的 Web 应用至关重要,它决定了 Servlet 如何初始化资源、处理请求以及释放资源。
Servlet 生命周期主要包含五个核心阶段:加载与实例化、初始化、服务就绪、请求处理和销毁。每个阶段都有明确的触发条件和执行逻辑,容器会按照预定的顺序调用相应的方法。
二、加载与实例化阶段
触发时机
- 默认行为:当 Servlet 第一次被请求时
- 配置行为 :通过
loadOnStartup属性配置为服务器启动时加载
执行过程
- 类加载:Servlet 容器通过类加载器加载 Servlet 类文件
- 实例化:容器调用 Servlet 的无参构造函数创建实例
关键特性
- 每个 Servlet 类通常只有一个实例(单例模式)
- 所有客户端请求共享同一个实例
- 线程安全成为核心问题
代码示例
java
运行
public class MyServlet extends HttpServlet {
/**
* 默认构造器 - 由Servlet容器调用
*/
public MyServlet() {
System.out.println("Servlet实例初始化完成");
}
}
三、初始化阶段
触发时机
- 实例化后立即执行
- 整个生命周期中只执行一次
执行过程
- 容器创建
ServletConfig对象 - 调用
init(ServletConfig config)方法
关键方法
java
运行
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config); // 必须调用父类初始化方法
// 获取初始化参数
String dbUrl = config.getInitParameter("databaseURL");
String userName = config.getInitParameter("userName");
// 初始化数据库连接池
connectionPool = createConnectionPool(dbUrl, userName);
System.out.println("Servlet初始化完成");
}
初始化参数配置
通过 web.xml 配置:
xml
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.example.MyServlet</servlet-class>
<init-param>
<param-name>databaseURL</param-name>
<param-value>jdbc:mysql://localhost:3306/mydb</param-value>
</init-param>
<init-param>
<param-name>userName</param-name>
<param-value>root</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
通过注解配置:
java
运行
@WebServlet(
name = "MyServlet",
urlPatterns = {"/myServlet"},
initParams = {
@WebInitParam(name = "databaseURL", value = "jdbc:mysql://localhost:3306/mydb"),
@WebInitParam(name = "userName", value = "root")
},
loadOnStartup = 1
)
最佳实践
- 资源初始化:数据库连接池、线程池、缓存等
- 加载配置文件
- 执行一次性计算
- 避免耗时操作(会延迟应用启动)
四、服务就绪阶段
状态特征
- Servlet 完成初始化
- 处于待命状态,等待客户端请求
- 可以立即处理请求
技术实现
- 容器维护 Servlet 实例在内存中
- 准备处理请求的线程池
- 建立请求 / 响应对象池(优化性能)
五、请求处理阶段
触发时机
- 每当有新的客户端请求被映射到该 Servlet 时
- 可以执行多次(每次请求都会调用)
核心流程
- 容器接收 HTTP 请求
- 创建或分配线程处理请求
- 创建
HttpServletRequest和HttpServletResponse对象 - 调用 Servlet 的
service()方法 - 根据 HTTP 方法路由到具体的
doXxx()方法 - 生成响应并返回客户端
- 容器回收请求 / 响应对象和线程
service () 方法实现
java
运行
/**
* 处理HTTP请求的核心服务方法
*/
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
// 转换为HTTP特定的请求/响应对象
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 根据HTTP方法进行路由分发
String method = request.getMethod();
switch (method) {
case "GET":
doGet(request, response);
break;
case "POST":
doPost(request, response);
break;
case "PUT":
doPut(request, response);
break;
case "DELETE":
doDelete(request, response);
break;
// 其他HTTP方法处理
}
}
doXxx () 方法实现
java
运行
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置响应内容类型
response.setContentType("text/html;charset=UTF-8");
// 获取请求参数
String name = request.getParameter("name");
String age = request.getParameter("age");
// 执行业务逻辑
String greeting = "Hello, " + (name != null ? name : "Guest");
// 生成响应
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>Servlet Example</title></head>");
out.println("<body>");
out.println("<h1>" + greeting + "</h1>");
out.println("<p>Age: " + (age != null ? age : "Not provided") + "</p>");
out.println("</body>");
out.println("</html>");
}
@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");
// 验证用户信息
boolean isValid = userService.validateUser(username, password);
if (isValid) {
// 登录成功
HttpSession session = request.getSession();
session.setAttribute("username", username);
response.sendRedirect("welcome.jsp");
} else {
// 登录失败
request.setAttribute("error", "用户名或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
关键特性
- 单实例多线程模型:每个请求在独立线程中执行
- 线程安全挑战 :
service()和doXxx()方法可被并发调用 - 请求 / 响应对象隔离:每个请求都有独立的对象实例
六、销毁阶段
触发时机
- Web 应用停止(服务器关闭)
- Web 应用重新部署
- Servlet 被动态卸载
执行过程
- 容器停止接受新请求
- 等待所有正在处理的请求完成(可配置超时)
- 调用
destroy()方法 - 销毁 Servlet 实例
- 执行垃圾回收
关键方法
java
运行
@Override
public void destroy() {
// 释放数据库连接池资源
if (connectionPool != null) {
connectionPool.close();
System.out.println("数据库连接池已关闭");
}
// 停止后台线程
if (backgroundThread != null && backgroundThread.isRunning()) {
backgroundThread.stop();
System.out.println("后台线程已停止");
}
// 保存应用状态
saveApplicationState();
System.out.println("Servlet资源释放完成");
}
最佳实践
- 关闭数据库连接
- 停止后台线程
- 释放文件句柄
- 保存应用状态
- 记录日志信息
七、关键特性深度解析
单例与线程安全
实现机制:
java
运行
public class ServletContainer {
// 缓存Servlet实例
private final Map<String, Servlet> servletCache = new ConcurrentHashMap<>();
public Servlet getServlet(String servletName) {
// 双重检查锁定实现线程安全
if (!servletCache.containsKey(servletName)) {
synchronized (this) {
if (!servletCache.containsKey(servletName)) {
Servlet servlet = createServlet(servletName);
servlet.init(config);
servletCache.put(servletName, servlet);
}
}
}
return servletCache.get(servletName);
}
}
线程安全问题:
java
运行
// 不安全场景
public class UnsafeServlet extends HttpServlet {
// 实例变量 - 线程不安全
private int counter = 0;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 多个线程同时修改counter变量
counter++;
response.getWriter().write("Counter: " + counter);
}
}
解决方案:
- 使用局部变量(推荐)
- 使用线程安全集合(如 ConcurrentHashMap)
- 添加同步机制(如 synchronized)
- 使用 ThreadLocal
ServletConfig 与 ServletContext 对比
| 特性 | ServletConfig | ServletContext |
|---|---|---|
| 作用范围 | 当前 Servlet | 整个 Web 应用 |
| 获取方式 | getServletConfig() | getServletContext() |
| 参数配置 | <init-param> | <context-param> |
| 生命周期 | 与 Servlet 一致 | 与 Web 应用一致 |
loadOnStartup 配置
- 正数:服务器启动时加载,数值越小优先级越高
- 0:服务器启动时加载,优先级最低
- 负数:默认行为,首次请求时加载
八、性能优化与最佳实践
资源管理优化
连接池管理:
java
运行
private DataSource dataSource;
@Override
public void init() throws ServletException {
super.init();
// 初始化连接池
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl(getServletConfig().getInitParameter("databaseURL"));
ds.setUsername(getServletConfig().getInitParameter("userName"));
ds.setPassword(getServletConfig().getInitParameter("password"));
// 连接池配置
ds.setInitialSize(5);
ds.setMaxActive(20);
ds.setMaxIdle(10);
ds.setMinIdle(2);
this.dataSource = ds;
}
线程安全优化
避免实例变量:
java
运行
// 正确做法 - 使用局部变量
public class GoodServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
User currentUser = getUserFromRequest(request); // 局部变量
processUser(currentUser); // 线程安全
}
}
缓存策略
页面缓存:
java
运行
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置缓存控制头
response.setHeader("Cache-Control", "public, max-age=3600");
response.setHeader("Expires", new Date(System.currentTimeMillis() + 3600000).toString());
// 检查缓存是否存在
String cacheKey = "page_" + request.getRequestURI();
String cachedContent = cacheService.get(cacheKey);
if (cachedContent != null) {
// 使用缓存内容
response.getWriter().write(cachedContent);
return;
}
// 生成页面内容
String content = generatePageContent();
// 缓存页面内容
cacheService.put(cacheKey, content, 3600);
response.getWriter().write(content);
}
异步处理
异步 Servlet 配置:
java
运行
@WebServlet(
urlPatterns = "/async",
asyncSupported = true // 启用异步支持
)
public class AsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 开启异步处理
AsyncContext asyncContext = request.startAsync();
// 设置超时时间
asyncContext.setTimeout(30000);
// 异步处理任务
executorService.submit(() -> {
try {
// 执行耗时操作
String result = longRunningOperation();
// 完成异步处理
asyncContext.getResponse().getWriter().write(result);
asyncContext.complete();
} catch (Exception e) {
asyncContext.complete();
}
});
}
}
九、常见问题与解决方案
内存泄漏问题
常见原因:
- 未关闭资源:数据库连接、文件流等
- 线程泄漏:创建的线程未正确停止
- 类加载器泄漏:静态变量引用导致类无法卸载
解决方案:
java
运行
@Override
public void destroy() {
// 关闭数据库连接池
if (dataSource != null) {
try {
((BasicDataSource) dataSource).close();
} catch (SQLException e) {
log.error("Failed to close data source", e);
}
}
// 停止线程池
if (executorService != null) {
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
}
// 清除静态引用
if (staticCache != null) {
staticCache.clear();
}
}
线程安全问题
问题诊断:
java
运行
// 使用ThreadLocal跟踪请求信息
private ThreadLocal<RequestContext> requestContext = new ThreadLocal<>();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
RequestContext context = new RequestContext();
context.setRequestId(UUID.randomUUID().toString());
context.setStartTime(System.currentTimeMillis());
requestContext.set(context);
try {
// 业务处理
processRequest(request, response);
} finally {
// 清除ThreadLocal,避免内存泄漏
requestContext.remove();
}
}
十、总结
Servlet 生命周期是 Java Web 开发的核心概念,理解其工作原理对于构建高性能、稳定的 Web 应用至关重要。通过掌握 Servlet 的加载、初始化、服务和销毁等各个阶段,开发者可以更好地管理资源、处理并发请求,并避免常见的性能和安全问题。
在实际开发中,我们应该:
- 合理管理资源:在 init () 方法中初始化资源,在 destroy () 方法中释放资源
- 确保线程安全:避免使用实例变量,使用线程安全的数据结构
- 优化性能:使用连接池、缓存等技术提高系统性能
- 处理异常:完善的异常处理机制保证系统稳定性
- 监控与调优:通过监控工具及时发现和解决问题
随着 Java Web 技术的发展,虽然出现了 Spring MVC 等更高级的框架,但 Servlet 作为 Java Web 的基础技术,仍然是每个 Java 开发者必须掌握的核心知识。理解 Servlet 生命周期不仅有助于我们更好地使用这些高级框架,也为我们解决复杂的 Web 开发问题提供了基础理论支持。