Servlet 生命周期
init / service / destroy 三个阶段的调用时机与线程模型。
Key Points容器加载类、调用一次 init(),之后每个请求一个线程调 service(),卸载时调一次 destroy()。
四个阶段
| Stage | When | How many times |
|---|---|---|
| Load | 首次请求或容器启动时 load-on-startup | 1 |
| init() | 实例创建后,处理请求前 | 1 |
| service() | 每个请求 | N |
| destroy() | 应用卸载或容器关闭 | 1 |
完整示例
Java
import java.io.IOException;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = "/life", loadOnStartup = 1)
public class LifeCycleServlet extends HttpServlet {
private long createdAt;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config); // 必须先调用,否则 getServletContext() 为 null
createdAt = System.currentTimeMillis();
log("init 完成,实例已创建");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
long alive = System.currentTimeMillis() - createdAt;
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().printf("已运行 %d 毫秒%n", alive);
}
@Override
public void destroy() {
log("destroy 被调用,释放资源");
}
}loadOnStartup = 1 makes the container create the instance and call init() at startup instead of on the first request. Lower values start earlier; a negative value defers to first access.
service 如何分派
HttpServlet.service() inspects the request method and forwards to the matching doXxx. An unimplemented method yields 405 rather than 404 — the mapping is right, the action simply is not implemented.
Java
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 自定义分派:所有方法都走这里
String method = req.getMethod();
if ("GET".equals(method)) {
doGet(req, resp);
} else if ("POST".equals(method)) {
doPost(req, resp);
} else {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, method);
}
}If
init() throws ServletException the container refuses to serve that servlet and later requests return 503. Keep initialisation fast; put connection pools and cache warm-up in a ServletContextListener instead of slowing startup here.并发下的三条规则
- 实例字段只放只读的初始化结果(配置、连接池引用)
- 请求相关数据一律用局部变量,天然线程安全
- 必须共享的可变状态用
AtomicInteger/ConcurrentHashMap/ 数据库
四个阶段
| 阶段 | 时机 | 次数 |
|---|---|---|
| 加载 | 首次请求或容器启动时 load-on-startup | 1 |
| 初始化 | 实例创建后,处理请求前 | 1 |
| 服务 | 每个请求 | N |
| 销毁 | 应用卸载或容器关闭 | 1 |
完整示例
Java
import java.io.IOException;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = "/life", loadOnStartup = 1)
public class LifeCycleServlet extends HttpServlet {
private long createdAt;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config); // 必须先调用,否则 getServletContext() 为 null
createdAt = System.currentTimeMillis();
log("init 完成,实例已创建");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
long alive = System.currentTimeMillis() - createdAt;
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().printf("已运行 %d 毫秒%n", alive);
}
@Override
public void destroy() {
log("destroy 被调用,释放资源");
}
}loadOnStartup = 1 让容器在启动时就创建实例并调用 init(),而不是等到第一个请求。值越小优先级越高,负数表示延迟到首次访问。
service 如何分派
HttpServlet.service() 先看请求方法,再转给对应的 doXxx。方法名不匹配时返回 405,而不是 404——说明 URL 映射是对的,只是没实现该动作。
Java
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 自定义分派:所有方法都走这里
String method = req.getMethod();
if ("GET".equals(method)) {
doGet(req, resp);
} else if ("POST".equals(method)) {
doPost(req, resp);
} else {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, method);
}
}init() 抛 ServletException 时容器不会把该 Servlet 加入服务,后续请求返回 503。初始化里做的事要尽量快——数据库连接、缓存预热应该放到 ServletContextListener 里,避免拖慢启动。并发下的三条规则
- 实例字段只放只读的初始化结果(配置、连接池引用)
- 请求相关数据一律用局部变量,天然线程安全
- 必须共享的可变状态用
AtomicInteger/ConcurrentHashMap/ 数据库