┌─────────────────────────────────────────────────────────────┐
│ Servlet 生命周期 │
├─────────────────────────────────────────────────────────────┤
│ 1. 加載類文件 → 2. 實例化 (new) → 3. init() 初始化 │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 類加載器讀取 反射調用無參構造 容器調用,僅一次 │
│ .class 文件 方法創建對象 可獲取 ServletConfig │
├─────────────────────────────────────────────────────────────┤
│ 4. service() 服務階段 (核心業務處理,多線程並發) │
│ │ │
│ ▼ │
│ 每次請求一調用,容器創建 Request/Response 對象傳入 │
│ 開發者責任:保證線程安全! │
├─────────────────────────────────────────────────────────────┤
│ 5. destroy() 銷毀階段 → 6. 實例被 GC 回收 │
│ │ │
│ ▼ │
│ 容器關閉/應用卸載前調用,釋放資源 (連接池、線程池、文件句柄) │
└─────────────────────────────────────────────────────────────┘
// Tomcat 內部簡化邏輯 (StandardWrapper.loadServlet)
public Servlet loadServlet() {
// 1. 類加載
Class> servletClass = loader.loadClass(servletClassName);
// 2. 實例化
Servlet servlet = (Servlet) servletClass.getDeclaredConstructor().newInstance();
// 3. 初始化
ServletConfig config = new StandardWrapperFacade(this); // 封裝初始化參數
servlet.init(config); // 僅調用一次!
return servlet;
}
關鍵點:
init(ServletConfig) 只調用一次,異常則標記不可用ServletConfig.getInitParameter("key") 讀取 web.xml 中的 <init-param>load-on-startup > 0 時啟動階段執行,否則首次請求時執行// HttpServlet.service() 核心邏輯
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 if ("PUT".equals(method)) {
doPut(req, resp);
} else if ("DELETE".equals(method)) {
doDelete(req, resp);
} else if ("HEAD".equals(method)) {
doHead(req, resp);
} else if ("OPTIONS".equals(method)) {
doOptions(req, resp);
} else if ("TRACE".equals(method)) {
doTrace(req, resp);
} else {
String errMsg = "HTTP method not supported: " + method;
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, errMsg);
}
}
線程模型:
service()public class UnsafeServlet extends HttpServlet {
private int counter = 0; // 實例變量:所有線程共享!
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
counter++; // 非原子操作:讀取-修改-寫入
resp.getWriter().println("計數: " + counter); // 併發下結果錯誤
}
}
| 方案 | 代碼示例 | 優點 | 缺點 |
|---|---|---|---|
| 局部變量 (推薦) | int count = 0; // 方法內部 | 天然線程安全、無鎖開銷 | 無法跨請求共享狀態 |
| ThreadLocal | ThreadLocal | 線程隔離、生命周期可控 | 需手動 remove() 防洩漏 |
| 原子類 | AtomicInteger counter = new AtomicInteger(); | 無鎖、高性能 | 僅適用簡單計數/狀態 |
| 同步鎖 | synchronized(this) { counter++; } | 通用、簡單 | 併發度低、死鎖風險 |
| 無狀態設計 (最佳) | Service/DAO 注入,Servlet 只做轉發 | 天然安全、易測試、符合分層 | 需依賴 IoC 容器 (Spring) |
public class ResourceServlet extends HttpServlet {
private DataSource dataSource;
private ScheduledExecutorService scheduler;
@Override
public void init() {
dataSource = createDataSource(); // 初始化連接池
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(this::cleanupTask, 1, 1, TimeUnit.HOURS);
}
@Override
public void destroy() {
// 1. 停止定時任務
if (scheduler != null) {
scheduler.shutdown();
try { if (!scheduler.awaitTermination(5, TimeUnit.SECONDS))
scheduler.shutdownNow(); }
catch (InterruptedException e) { scheduler.shutdownNow(); }
}
// 2. 關閉連接池
if (dataSource instanceof AutoCloseable) {
try { ((AutoCloseable)dataSource).close(); } catch (Exception e) { log(e); }
}
// 3. 其他資源清理
super.destroy();
}
}
注意:容器調用 destroy() 後,不再分發請求給該實例,但正在執行的 service() 線程會繼續運行至結束。destroy() 中不應等待這些線程 (除非業務強要求),避免關閉超時。
@WebServlet(
urlPatterns = {"/user", "/user/*"},
loadOnStartup = 1,
initParams = {
@WebInitParam(name = "pageSize", value = "20"),
@WebInitParam(name = "cacheEnabled", value = "true")
},
asyncSupported = true // 支持異步處理
)
public class ModernServlet extends HttpServlet {
@Override
public void init() {
int pageSize = Integer.parseInt(getServletConfig().getInitParameter("pageSize"));
// ...
}
}
無需 web.xml,容器掃描註解自動註冊。asyncSupported=true 允許使用 req.startAsync() 進行異步處理 (長輪詢、推送)。
用戶瀏覽器
│
▼ HTTP Request
┌─────────────────────────────────────┐
│ Tomcat Connector (NIO/BIO) │
│ 解析 HTTP 協議 → 生成 Request/Response │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Engine → Host → Context (Web應用) │
│ 根據 url-pattern 匹配 Servlet │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Filter Chain (doFilter) │
│ EncodingFilter → SecurityFilter → ...│
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Servlet.service(req, resp) │
│ ├─ HttpServlet.service() │
│ │ └─ 分發 doGet/doPost... │
│ │ ├─ 參數解析/校驗 │
│ │ ├─ 業務邏輯調用 (Service) │
│ │ └─ 結果渲染/轉發 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Filter Chain (反向) │
│ 响應頭/體最終提交給 Connector │
└─────────────────────────────────────┘
│
▼ HTTP Response
用戶瀏覽器
doGet/doPostinit() 初始化,destroy() 釋放,配合 try-with-resourcesweb.xml 配置 <error-page> 或 Spring MVC @ControllerAdviceAsyncContext ctx = req.startAsync(); ctx.start(() -> { ... ctx.complete(); })理解 Servlet 生命周期與線程模型,是排查併發 Bug、優化性能、設計高可用 Web 應用的基石。現代框架 (Spring MVC, WebFlux) 雖屏蔽了細節,但底層契約始終未變。