Servlet 是 Java EE 规范中用于扩展 Web 服务器功能的核心组件。理解 Servlet 的生命周期对于开发健壮、高效的 Web 应用至关重要。Servlet 的生命周期由 Web 容器(如 Tomcat, Jetty)严格管理,主要包含三个阶段:初始化(Initialization) 、请求处理(Request Handling) 和 销毁(Destruction)。
1. 加载、实例化与初始化 (init())
- 触发时机:当 Web 应用启动时,或者当客户端首次请求访问该 Servlet 时(取决于配置)。
- 过程 :
- 加载 :Web 容器根据部署描述符(
web.xml)或注解(如@WebServlet)定位 Servlet 类。 - 实例化 :容器调用 Servlet 类的无参构造函数,创建一个 Servlet 实例。注意: 通常一个 Servlet 类在整个 Web 应用中只被实例化一次(单例模式),处理后续所有请求。
- 初始化 :在实例化后,容器立即调用该实例的
init(ServletConfig config)方法。ServletConfig对象:包含 Servlet 的初始化参数(在web.xml中配置或通过@WebInitParam注解指定)和 ServletContext 引用。- 用途 :开发者可以在此方法中进行一次性的初始化工作,例如:
- 建立数据库连接池
- 加载配置文件
- 初始化资源
- 执行其他只需执行一次的设置任务
- 加载 :Web 容器根据部署描述符(
- 关键点 :
init()方法只会在 Servlet 实例的生命周期中被调用一次。- 如果初始化失败(例如抛出
ServletException),该 Servlet 实例将不会被投入使用,容器可能会记录错误。
java
public class MyServlet extends HttpServlet {
private SomeResource resource; // 示例资源
@Override
public void init() throws ServletException {
// 通常重写无参的 init() 方法更方便
super.init();
// 执行初始化逻辑,例如:
resource = initializeResource(); // 初始化资源
}
// ... 其他方法
}
2. 请求处理 (service() -> doGet(), doPost(), etc.)
- 触发时机:每当有新的客户端请求(HTTP 请求)被映射到该 Servlet 时。
- 过程 :
- 接收请求:Web 容器接收到一个指向该 Servlet 的 HTTP 请求。
- 创建对象 :容器创建代表请求的
HttpServletRequest对象和代表响应的HttpServletResponse对象。 - 调用
service():容器将request和response对象作为参数,调用 Servlet 实例的service(HttpServletRequest req, HttpServletResponse resp)方法。 - 分发请求 :在
HttpServlet类(绝大多数 Servlet 的基类)中,service()方法会根据 HTTP 请求的方法(GET, POST, PUT, DELETE 等)调用相应的doXxx()方法。- 例如,GET 请求会调用
doGet(HttpServletRequest req, HttpServletResponse resp)。 - POST 请求会调用
doPost(HttpServletRequest req, HttpServletResponse resp)。
- 例如,GET 请求会调用
- 执行业务逻辑 :开发者通常在
doGet(),doPost()等方法中编写处理请求的核心业务逻辑:- 读取请求参数
- 访问数据库
- 执行业务计算
- 生成响应内容(HTML, JSON, XML 等)
- 设置响应头、状态码
- 将响应发送回客户端
- 关键点 :
- 此阶段是 Servlet 生命周期中最活跃的部分,会被反复调用以处理多个并发请求。
- 线程安全 :由于 Servlet 是单例的,其
service()和doXxx()方法会被多个线程同时访问。开发者必须谨慎处理实例变量(成员变量)的并发访问,避免线程安全问题。 通常建议使用局部变量或线程安全的结构。 - 请求对象 (
request) 和响应对象 (response) 的生命周期仅限于单个请求的处理过程。
java
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 处理 GET 请求的逻辑
String param = request.getParameter("name"); // 获取请求参数
// ... 业务处理 ...
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>Hello, " + param + "!</body></html>"); // 生成响应
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 处理 POST 请求的逻辑
}
}
3. 销毁 (destroy())
- 触发时机 :
- Web 应用被停止或卸载时(例如服务器关闭、应用重新部署)。
- Servlet 长时间空闲后(取决于容器实现,不常见)。
- 过程 :容器在回收 Servlet 实例之前,会调用其
destroy()方法。 - 用途 :开发者可以在此方法中进行清理工作,释放
init()方法中分配的资源,例如:- 关闭数据库连接
- 保存状态到持久存储
- 释放文件句柄、网络连接等
- 关键点 :
destroy()方法在 Servlet 生命周期中只被调用一次。- 一旦
destroy()被调用,该 Servlet 实例将不再处理任何新的请求。 - 容器保证在调用
destroy()之前,所有正在该 Servlet 上执行的service()线程(处理请求的线程)都已完成或超时终止。 - 在此方法中,可以访问 ServletContext 获取应用级别的信息,但不能再访问请求或响应对象。
java
public class MyServlet extends HttpServlet {
private SomeResource resource; // 在 init() 中初始化
@Override
public void destroy() {
// 执行清理逻辑,例如:
if (resource != null) {
resource.cleanup(); // 清理资源
}
super.destroy();
}
}
生命周期流程图
graph LR
A[Web 应用启动/首次请求] --> B[容器加载 Servlet 类]
B --> C[容器实例化 Servlet
调用构造函数] C --> D[容器调用 init(ServletConfig)] D --> E[Servlet 就绪] E --> F{客户端请求到达?} F -- Yes --> G[容器创建 request/response 对象] G --> H[容器调用 service(request, response)] H --> I[service 方法根据请求方法
调用 doGet/doPost/等] I --> J[执行业务逻辑
生成响应] J --> K[发送响应] K --> F F -- No --> L{应用停止/卸载?} L -- Yes --> M[容器调用 destroy()] M --> N[释放资源] N --> O[Servlet 实例被垃圾回收] L -- No --> F
调用构造函数] C --> D[容器调用 init(ServletConfig)] D --> E[Servlet 就绪] E --> F{客户端请求到达?} F -- Yes --> G[容器创建 request/response 对象] G --> H[容器调用 service(request, response)] H --> I[service 方法根据请求方法
调用 doGet/doPost/等] I --> J[执行业务逻辑
生成响应] J --> K[发送响应] K --> F F -- No --> L{应用停止/卸载?} L -- Yes --> M[容器调用 destroy()] M --> N[释放资源] N --> O[Servlet 实例被垃圾回收] L -- No --> F
总结
Servlet 的生命周期是一个清晰定义的模型:
init()一次:用于初始化。service()/doXxx()多次:用于处理请求(核心)。destroy()一次:用于清理。
Web 容器负责精确管理这个生命周期。开发者应遵循这个模型,在正确的方法中编写相应的代码(初始化在 init(),业务逻辑在 doGet()/doPost() 等,清理在 destroy()),并特别注意 Servlet 单例特性带来的线程安全问题。理解并合理利用 Servlet 生命周期是构建稳定 Java Web 应用的基础。