Servlet 是 Java Web 開發的基石,理解 Servlet 規範是深入學習 Web 容器的前提。本章涵蓋 HTTP 協定基礎、Servlet 規範、生命週期管理、Filter 與 Listener 機制,以及 Session 管理。

HTTP 協定基礎#

HTTP 的本質#

HTTP 協定的本質是一種通信格式

HTTP 是信封,HTML 是信的內容。沒有信封,信也寄不出去。

HTTP 協定是瀏覽器與伺服器之間的資料傳送協定。作為應用層協定,HTTP 基於 TCP/IP 來傳遞資料,主要規定了用戶端和伺服器之間的通信格式。

HTTP 請求處理流程#

sequenceDiagram
    participant B as 瀏覽器
    participant S as 伺服器

    B->>S: 1. 建立 TCP 連線
    B->>S: 2. 發送 HTTP 請求<br/>(請求行 + 請求頭 + 請求體)
    S-->>B: 3. 回傳 HTTP 回應<br/>(狀態行 + 回應頭 + 回應體)
    B->>S: 4. 關閉連線(或保持連線)

HTTP 請求格式#

POST /login HTTP/1.1              ← 請求行:方法 + URL + 協定版本
Host: www.example.com             ← 請求頭
Content-Type: application/json
Content-Length: 42

{"username":"admin","password":"123"}  ← 請求體

HTTP 回應格式#

HTTP/1.1 200 OK                   ← 狀態行:協定版本 + 狀態碼 + 原因短語
Content-Type: application/json    ← 回應頭
Content-Length: 28

{"status":"success","token":"xxx"}  ← 回應體

Servlet 規範#

為什麼需要 Servlet 規範?#

早期的 Web 應用主要用於瀏覽靜態頁面。隨著需求發展,我們需要動態生成內容,這就需要一種機制讓 HTTP 伺服器能呼叫服務端程序。

問題:HTTP 伺服器怎麼知道要呼叫哪個 Java 類的哪個方法?

解決方案:面向介面編程

flowchart LR
    HTTP["HTTP 伺服器"] --> SC["Servlet 容器<br/><i>(解耦)</i>"]
    SC --> S["Servlet<br/>(業務邏輯)"]

    style HTTP fill:#ffccbc
    style SC fill:#fff9c4
    style S fill:#c8e6c9

Servlet 介面是 Servlet 容器與具體業務類之間的契約

這個設計讓 HTTP 伺服器與業務邏輯解耦,你只需要實現 Servlet 介面,容器負責管理和呼叫。

Servlet 介面定義#

public interface Servlet {
    // 初始化方法,容器加載 Servlet 時呼叫
    void init(ServletConfig config) throws ServletException;

    // 獲取 Servlet 組態
    ServletConfig getServletConfig();

    // 核心方法:處理請求
    void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException;

    // 獲取 Servlet 資訊
    String getServletInfo();

    // 銷毀方法,容器卸載 Servlet 時呼叫
    void destroy();
}

Servlet 繼承體系#

graph TD
    S["Servlet<br/><i>(介面)</i>"]
    S --> GS["GenericServlet<br/><i>(抽象類,協定無關)</i>"]
    GS --> HS["HttpServlet<br/><i>(抽象類,HTTP 特化)</i>"]
    HS --> YS["你的 Servlet<br/><i>(繼承 HttpServlet)</i>"]

    style S fill:#e3f2fd
    style GS fill:#fff9c4
    style HS fill:#ffccbc
    style YS fill:#c8e6c9

實際開發中,我們只需繼承 HttpServlet 並重寫 doGetdoPost 等方法:

public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.println("<h1>Hello, Servlet!</h1>");
    }

    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {
        // 處理 POST 請求
    }
}

Servlet 生命週期#

生命週期階段#

flowchart LR
    L["加載類<br/>(類加載器)"] --> N["實體化<br/>(new)"]
    N --> I["初始化<br/>(init)"]
    I --> S["服務<br/>(service)"]
    S -->|多次呼叫| S
    S -->|容器關閉| D["銷毀<br/>(destroy)"]

    style L fill:#e3f2fd
    style I fill:#fff9c4
    style S fill:#c8e6c9
    style D fill:#ffcdd2
階段時機說明
加載首次請求或啟動時容器加載 Servlet 類到 JVM
實體化加載後容器創建 Servlet 實體(通常是單例)
初始化實體化後呼叫 init() 方法,只呼叫一次
服務每次請求呼叫 service() 方法處理請求
銷毀容器關閉時呼叫 destroy() 方法,釋放資源

Servlet 默認是單例的

一個 Servlet 類在容器中通常只有一個實體,多個請求由多個線程共享這個實體。因此,Servlet 中不應該使用實體變數保存請求相關的資料,否則會有線程安全問題。

延遲加載與預加載#

默認情況下,Servlet 採用延遲加載策略,即第一次請求時才創建實體。

可以通過 loadOnStartup 參數組態預加載

<servlet>
    <servlet-name>myServlet</servlet-name>
    <servlet-class>com.example.MyServlet</servlet-class>
    <load-on-startup>1</load-on-startup>  <!-- 數字越小,優先級越高 -->
</servlet>

或使用註解:

@WebServlet(urlPatterns = "/my", loadOnStartup = 1)
public class MyServlet extends HttpServlet {
    // ...
}

Filter 與 Listener#

Filter(過濾器)#

Filter 是 Servlet 規範提供的擴展機制,用於對請求和回應進行統一的預處理和後處理。

典型應用場景

  • 字符編碼設置
  • 登錄驗證
  • 權限檢查
  • 日誌記錄
  • 請求限流

Filter 鏈的工作原理#

flowchart LR
    subgraph Request["請求流程"]
        direction LR
        R[請求] --> F1[Filter1] --> F2[Filter2] --> F3[Filter3] --> S[Servlet]
    end

    subgraph Response["回應流程"]
        direction RL
        S2[Servlet] --> F3R[Filter3] --> F2R[Filter2] --> F1R[Filter1] --> Res[回應]
    end

    style S fill:#c8e6c9
    style S2 fill:#c8e6c9
public interface Filter {
    void init(FilterConfig filterConfig) throws ServletException;

    void doFilter(ServletRequest request,
                  ServletResponse response,
                  FilterChain chain) throws IOException, ServletException;

    void destroy();
}

Filter 實現示例#

@WebFilter("/*")
public class EncodingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException {
        // 前置處理
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        // 呼叫下一個 Filter 或 Servlet
        chain.doFilter(request, response);

        // 後置處理(可選)
    }
}

Filter 鏈的執行順序

  • web.xml 組態:按 <filter-mapping> 的聲明順序
  • 註解組態:按 Filter 類名的字母順序

Listener(監聽器)#

Listener 用於監聽容器內部發生的事件,主要分為兩類:

類型監聽器介面監聯事件
生命週期ServletContextListenerContext 啟動/停止
HttpSessionListenerSession 創建/銷毀
ServletRequestListener請求到達/處理完成
屬性變化ServletContextAttributeListenerContext 屬性變化
HttpSessionAttributeListenerSession 屬性變化
ServletRequestAttributeListenerRequest 屬性變化

Listener 實現示例#

@WebListener
public class AppContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // Web 應用啟動時執行
        System.out.println("Web 應用正在啟動...");
        // 初始化全局資源,如資料庫連線池
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // Web 應用關閉時執行
        System.out.println("Web 應用正在關閉...");
        // 釋放全局資源
    }
}

Spring 就是通過 ServletContextListener 來啟動的

Spring 實現了 ContextLoaderListener,監聽 ServletContext 的啟動事件,在 Web 應用啟動時創建並初始化 Spring 容器。

Filter vs Listener 的本質區別#

特性FilterListener
工作方式干預過程監聯狀態
觸發時機每次請求事件發生時
作用物件請求/回應容器/Session/請求的狀態
是否能修改可以修改請求和回應只能回應事件,不能修改

Session 管理#

為什麼需要 Session?#

HTTP 協定是無狀態的

每次請求之間沒有關聯,伺服器不知道請求來自哪個用戶。Session 機制解決了用戶身份識別問題。

flowchart LR
    subgraph Browser["瀏覽器"]
        Cookie["Cookie:<br/>JSESSIONID=ABC123"]
    end

    subgraph Server["伺服器"]
        Session["Session:<br/>用戶資料<br/>購物車<br/>登錄狀態"]
    end

    Cookie <-->|"對應關係"| Session

    style Browser fill:#e3f2fd
    style Server fill:#c8e6c9

工作流程

  1. 用戶首次訪問,伺服器創建 Session,生成唯一的 Session ID
  2. Session ID 通過 Cookie 發送給瀏覽器
  3. 瀏覽器後續請求攜帶 Cookie(包含 Session ID)
  4. 伺服器根據 Session ID 找到對應的 Session 資料

Session 的創建與使用#

// 獲取 Session,不存在則創建
HttpSession session = request.getSession();  // 等同於 getSession(true)

// 獲取 Session,不存在回傳 null
HttpSession session = request.getSession(false);

// 存儲資料
session.setAttribute("user", userObject);

// 讀取資料
User user = (User) session.getAttribute("user");

// 移除資料
session.removeAttribute("user");

// 銷毀 Session
session.invalidate();

Session 組態#

web.xml 組態 Session 逾時時間
<session-config>
    <!-- 逾時時間,單位:分鐘 -->
    <session-timeout>30</session-timeout>

    <!-- Cookie 組態 -->
    <cookie-config>
        <name>JSESSIONID</name>
        <http-only>true</http-only>
        <secure>true</secure>
        <max-age>1800</max-age>
    </cookie-config>

    <!-- Session 追蹤模式 -->
    <tracking-mode>COOKIE</tracking-mode>
</session-config>

Tomcat 的 Session 管理#

Tomcat 提供了多種 Session 持久化方案:

方案說明適用場景
StandardManager默認,內存存儲,重啟丟失開發環境
PersistentManager持久化到文件系統單機生產
DeltaManager集群 Session 複製小規模集群
BackupManager集群 Session 備份大規模集群
Redis Session使用 Redis 存儲微服務、高可用

現代應用的 Session 管理趨勢

在微服務架構下,建議使用 Redis 等外部存儲來管理 Session,實現 Session 共享和高可用。Spring Session 是一個很好的選擇。

Web 應用目錄結構#

Servlet 規範定義了標準的 Web 應用目錄結構:

MyWebApp/
├── WEB-INF/
│   ├── web.xml           ← 部署描述符(可選,Servlet 3.0+)
│   ├── classes/          ← 編譯後的類文件
│   │   └── com/
│   │       └── example/
│   │           └── MyServlet.class
│   └── lib/              ← 依賴的 JAR 包
│       └── xxx.jar
├── META-INF/             ← 元資訊目錄
│   └── MANIFEST.MF
├── index.html            ← 靜態資源
├── css/
├── js/
└── images/

WEB-INF 目錄的特殊性

WEB-INF 目錄下的內容不能被用戶端直接訪問,只能通過 Servlet 轉發。這是保護組態文件和類文件安全的機制。

總結#

概念作用關鍵點
HTTP 協定通信格式請求/回應格式、無狀態
Servlet業務處理生命週期、線程安全
Filter請求過濾責任鏈模式、可組合
Listener事件監聽生命週期事件、屬性變化
Session狀態管理Cookie 關聯、分布式存儲