Servlet 是 Java Web 的基石,理解其生命周期與工作原理,是掌握 Spring MVC、Filter、Listener 等上層框架的前提。本文系統梳理三大階段、加載時機、Service 分發邏輯、Tomcat 參數解析、銷毀觸發條件,並對比 Servlet 與 JSP 定位差異。
| 條件 | 說明 |
| 啟動加載 | web.xml <load-on-startup>1</load-on-startup>,容器啟動時實例化並 init。 |
| 首次請求 | 無 load-on-startup,首次訪問時創建實例。 |
| 類文件更新 | 開發模式下熱部署觸發重載,舊實例 destroy,新實例 init。 |
無 <servlet-mapping> 的 Servlet:配合 load-on-startup 用於啟動時初始化數據源、緩存、定時任務等全局資源。
三、請求處理核心流程
1. 入口:service 方法分發
// HttpServlet 源碼簡化
protected void service(HttpServletRequest req, HttpServletResponse resp) {
String method = req.getMethod();
if ("GET".equals(method)) doGet(req, resp);
else if ("POST".equals(method)) doPost(req, resp);
// ... PUT, DELETE, HEAD, OPTIONS, TRACE
else throw new ServletException("不支持的方法: " + method);
}
GenericServlet 實現 Servlet 接口,提供 init/config/log 默認實現。
HttpServlet 繼承 GenericServlet,實現 HTTP 協議綁定:將 ServletRequest/Response 強轉為 HttpServletRequest/Response,再調用無參 service(req, resp) 分發到 doXxx。
- 開發者必須重寫 doGet/doPost 等,父類默認返回 405/400 錯誤。
2. Tomcat 參數解析機制
// 內部實現簡化
private Hashtable<String, String[]> paramHashStringArray = new Hashtable<>();
// 解析 query string / body 後填充 Map
// 獲取:request.getParameter("key") → String
// 獲取多值:request.getParameterValues("key") → String[]
- 使用 Hashtable(線程安全)而非 HashMap,因參數解析階段可能多線程並發寫入(早期版本)。
- 現代 Tomcat 優化為無鎖解析 + 不可變快照,但對外 API 保持一致。
四、線程模型與共享狀態風險
- Servlet 實例單例多線程:容器僅創建一個實例服務所有請求。
- 實例變量非線程安全:
private int count; 并发訪問會數據競態。
- 解決方案:
- 無狀態設計:不定義實例變量,所有數據放方法局部變量。
- ThreadLocal 隔離:每線程獨享副本。
- 同步鎖 / 原子類:性能較差,慎用。
五、銷毀階段與資源釋放
public void destroy() {
// 關閉數據庫連接池、線程池、文件句柄、定時任務
// 移除 ThreadLocal 引用防內存洩漏
super.destroy();
}
觸發場景:應用卸載、容器關閉、熱部署重載、Servlet 類刪除。
六、Servlet vs JSP 定位對比
| 維度 | Servlet | JSP |
| 本質 | Java 類 | 模板文件(編譯為 Servlet) |
| 擅長 | 業務邏輯、流程控制、數據處理 | 頁面渲染、標籤庫、前端展示 |
| 調試 | 斷點、單步、重構友好 | 行號映射複雜、EL 表達式難調試 |
| 職責分離 | Controller / Service | View |
最佳實踐:Servlet 做控制器分發,JSP/Thymeleaf/Freemarker 做視圖渲染,嚴禁在 JSP 中寫業務邏輯、JDBC 代碼。
七、Web 應用啟動順序
- 容器啟動 → 掃描
web.xml / ServletContainerInitializer。
- 實例化
load-on-startup Servlet、ServletContextListener、Filter。
- 調用
contextInitialized → 初始化 Spring 容器、連接池等。
- 應用就緒,接收請求。
八、總結
Servlet 生命周期由容器完全托管,開發者聚焦於 init 初始化資源、doXxx 處理業務、destroy 釋放資源。牢記「單例多線程、無狀態設計」,配合 Filter/Listener 構建完整 Web 應用骨架。
- 上一篇文章: JSON 數據交互最佳實踐:Map 封裝解決 JSONObject 轉義難題
- 下一篇文章: Spring 整合 Quartz 實戰:聲明式定時任務配置全指南
Copyright ©
程式碼之家 All Rights Reserved