Servlet 技術演進與生命週期深度解析
Servlet 是 Java Web 開發的基石。理解其誕生背景、核心特點與生命週期,有助於我們寫出高效、穩健的伺服器端程式。
一、伺服器端技術演進史:為什麼需要 Servlet?
1. CGI 時代(Common Gateway Interface)
- 模式:每個請求啟動一個獨立進程。
- 痛點:
- 進程建立/銷毀開銷極大,併發能力極弱。
- 進程間隔離,無法共享記憶體資源(如資料庫連接池、快取)。
- 開發語言不限,但易受作業系統進程模型影響,錯誤難以排查。
2. 進程池技術
- 預先啟動一組進程,請求到來時分配空閒進程。
- 緩解了頻繁建立進程的開銷,但本質仍是重量級進程,資源消耗大,擴展性受限。
3. Servlet 技術誕生
- 核心突破:基於多線程模型,單一 JVM 進程處理所有請求。
- 優勢:
- 線程輕量,建立/切換成本低,支撐高併發。
- 同一進程內線程共享堆記憶體,天然支援資源共享(連接池、Session、Application 級快取)。
- Java 語言特性帶來跨平台、記憶體安全、豐富的生態庫。
二、Servlet 核心特點
- 容器管理:Servlet 由 Web 容器(Tomcat、Jetty 等)實例化、初始化、銷毀,開發者不直接 `new Servlet()`。
- 請求驅動:容器接收 HTTP 請求,封裝為 `HttpServletRequest`/`Response`,調用 Servlet 的 `service()` 方法。
- 非 JavaBean:生命週期受容器控制,擁有 `init()`、`destroy()` 等回調方法。
- 單實例多線程:預設情況下,容器為每個 Servlet 類只建立一個實例,所有請求共享該實例,透過線程隔離請求數據。
三、Servlet 生命週期完整流程
public interface Servlet {
// 1. 初始化:容器建立實例後,僅呼叫一次
void init(ServletConfig config) throws ServletException;
// 2. 服務:每個請求一條線程呼叫,核心業務邏輯入口
void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
// 3. 銷毀:容器卸載 Servlet 前呼叫,僅一次,釋放資源
void destroy();
// 輔助方法
ServletConfig getServletConfig();
String getServletInfo();
}
生命週期階段詳解
| 階段 | 觸發時機 | 執行次數 | 關鍵動作 | 線程安全 |
| 載入與實例化 |
容器啟動時(配置 `load-on-startup`)或首次請求到達 |
1 次 |
載入類檔案
透過無參建構子建立實例
單線程 |
| 初始化 (init) |
實例化後立即 |
1 次 |
讀取初始化參數 (`ServletConfig`)
建立昂貴資源:資料庫連接池、快取、線程池
單線程 |
| 請求處理 (service) |
每個 HTTP 請求到達 |
N 次 |
解析請求、執行業務邏輯、生成回應
HttpServlet 會分派至 doGet/doPost 等
多線程併發,需注意共享變數安全 |
| 銷毀 |
容器關閉、專案重新部署、Servlet 移除 |
1 次 |
釋放資源:關閉連接池、儲存狀態、停止排程任務
單線程(等待所有 service 線程結束) |
四、HttpServlet 請求分派機制
`HttpServlet` 覆寫了 `service()`,根據 HTTP 方法分派:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals("GET")) {
doGet(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} // ... PUT, DELETE, HEAD, OPTIONS, TRACE
}
開發者通常只需覆寫 `doGet`/`doPost` 等具體方法。
五、線程安全實戰原則
- 避免實例變數存儲請求狀態:請求參數、業務中間結果請放在方法區域變數(棧上),天然線程安全。
- 共享資源加鎖或使用線程安全類:如 `AtomicInteger`、`ConcurrentHashMap`、`ThreadLocal`。
- 唯讀實例變數安全:`final` 修飾的不可變物件、初始化後不再修改的配置資訊。
- 慎用 `SingleThreadModel`:已棄用,效能極差,不解決 Session/Application 層面共享問題。
六、最佳實踐建議
- 繼承 `HttpServlet` 而非直接實作 `Servlet` 介面。
- 在 `init()` 中完成昂貴資源初始化,`destroy()` 中確實釋放。
- 業務邏輯委託給 Service 層,Servlet 僅負責協議轉換與流程控制。
- 善用 `ServletContextListener` 管理應用級資源(比 Servlet 生命週期更宏觀)。
- 配置 `async-supported="true"` 支援 Servlet 3.0+ 非同步處理,提升長連接/耗時操作吞吐量。
Servlet 雖是「老」技術,但 Spring MVC、Struts 等主流框架底層皆建立於此。吃透其生命週期與線程模型,是進階 Java Web 工程師的必經之路。