Servlet 是 Java Web 開發的基石。本文系統涵蓋:Servlet 核心介面、生命週期、初始化參數、ServletConfig/ServletContext 應用、URL 映射規則、執行緒安全、預設 Servlet、負載啟動等完整知識體系,並附帶最佳實踐與常見坑點。
javax.servlet.Servlet(5 方法:init、service、destroy、getServletConfig、getServletInfo)GenericServlet(協定無關)、HttpServlet(HTTP 專用,已實作 service 分派 doGet/doPost)HttpServlet,覆寫 doGet/doPost,勿覆寫 service。@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("Hello, Servlet!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
}
Servlet 3.0+ 推薦用 @WebServlet 註解替代 web.xml。
| 階段 | 觸發時機 | 方法 | 呼叫次數 |
|---|---|---|---|
| 初始化 | 首次請求或啟動時 (load-on-startup) | init() | 1 次 |
| 服務 | 每次請求 | service() → doGet/doPost | N 次 |
| 銷毀 | 容器關閉/應用重載 | destroy() | 1 次 |
<load-on-startup>@WebServlet(value = "/init", loadOnStartup = 1)
public class InitServlet extends HttpServlet {
@Override public void init() {
// 初始化連線池、載入配置、建立資料表...
}
}
數值越小優先級越高;正整數才會啟動載入。
@WebServlet(value = "/config", initParams = {
@WebInitParam(name = "encoding", value = "UTF-8"),
@WebInitParam(name = "maxSize", value = "1048576")
})
public class ConfigServlet extends HttpServlet {
@Override public void init(ServletConfig config) {
String encoding = config.getInitParameter("encoding");
// ...
}
}
<context-param> → context.getInitParameter()setAttribute/getAttribute/removeAttribute,所有 Servlet 可見,適合快取、共享資料源。getResourceAsStream("/WEB-INF/config.properties")、getRealPath()、getRequestDispatcher() 轉發/包含。// Servlet A 寫入
context.setAttribute("appConfig", configObj);
// Servlet B 讀取
AppConfig cfg = (AppConfig) context.getAttribute("appConfig");
| 模式 | 範例 | 優先順序 |
|---|---|---|
| 精確路徑 | /user/profile | 最高 |
| 目錄通配 | /user/* | 次之 |
| 副檔名通配 | *.do, *.action | 再次之 |
| 預設 Servlet | / | 最低 |
匹配原則:「最長路徑優先」→「目錄通配」→「副檔名通配」→「預設」。
public class UnsafeServlet extends HttpServlet {
private int count = 0; // 實例變數 = 共享變數
protected void doGet(...) {
count++; // 非原子操作,併發下會丟失更新
resp.getWriter().write("Count: " + count);
}
}
| 方案 | 程式碼 | 優點 | 缺點 |
|---|---|---|---|
| 區域變數 | int count = 0; 在 doGet 內 | 天然線程安全,無鎖開銷 | 無法跨請求保持狀態 |
| ThreadLocal | ThreadLocal<Integer> tl = new ThreadLocal<>(); | 隔離執行緒狀態 | 記憶體洩漏風險,需手動 remove |
| 同步鎖 | synchronized(this) { count++; } | 簡單 | 嚴重降低併發吞吐 |
| 原子類別 | AtomicInteger count = new AtomicInteger(); | 無鎖高併發 | 僅適用簡單計數/累加 |
| 無狀態設計 | Servlet 不保存狀態,狀態外移至 Redis/DB | 水平擴展最佳 | 架構複雜度提升 |
黃金法則:Servlet 必須無狀態。所有請求相關資料放在區域變數、Request 屬性或外部分散式快取。
Tomcat conf/web.xml 內建 DefaultServlet 映射 /,處理:靜態檔案 (HTML/CSS/JS/圖片)、目錄列表、預設歡迎頁。自訂 / 映射會覆蓋它,慎用。
@WebServlet 註解,棄用 web.xml。HttpServlet,只覆寫 doGet/doPost。ServletContext 屬性或外部存儲。init() + loadOnStartup=1。resp.setContentType("application/json;charset=UTF-8")。<error-page> 或 Filter 處理,Servlet 不捕獲棧追蹤。Servlet 雖非現代框架 (Spring MVC, Jakarta REST) 的直接開發介面,但它是所有框架的運行基座。精通其生命週期、共享機制、URL 映射、執行緒模型,能讓你在排查疑難雜症、調優效能、設計架構時擁有底層視野。