Tomcat 是最流行的 Java Web 容器,它的設計非常經典,運用了大量設計模式和 Java 高級技術。深入理解 Tomcat 架構,不僅能幫助我們更好地使用和調校 Tomcat,還能從中學習系統設計的精髓。

整體架構#

兩大核心組件#

Tomcat 的核心功能

  1. 處理 Socket 連線,負責網路字節流與 Request/Response 物件的轉換
  2. 加載和管理 Servlet,處理具體的請求

因此 Tomcat 設計了**連線器(Connector)容器(Container)**兩個核心組件。

flowchart LR
    Client[用戶端請求] --> HTTP[Connector<br>HTTP]
    Client --> AJP[Connector<br>AJP]

    subgraph Server
        subgraph Service
            HTTP --> Engine[Container<br>Engine]
            AJP --> Engine
        end
    end

組件層次關係#

組件作用數量關係
ServerTomcat 實體一個 Tomcat 程序對應一個 Server
Service服務組件,包裝連線器和容器一個 Server 可以有多個 Service
Connector連線器,處理網路通信一個 Service 可以有多個 Connector
Engine引擎,管理虛擬主機一個 Service 只有一個 Engine
Host虛擬主機一個 Engine 可以有多個 Host
ContextWeb 應用一個 Host 可以有多個 Context
WrapperServlet 包裝器一個 Context 可以有多個 Wrapper

連線器(Connector)設計#

連線器的職責#

連線器負責對外交流,主要完成以下工作:

  1. 監聯網路連線埠
  2. 接受網路連線請求
  3. 讀取網路請求字節流
  4. 根據應用層協定(HTTP/AJP)解析字節流
  5. 生成 Tomcat Request 物件
  6. 將 Tomcat Request 轉成 ServletRequest
  7. 呼叫 Servlet 容器處理請求
  8. 將回應轉換回網路字節流

三大核心組件#

高內聚、低耦合的設計

Tomcat 將連線器的功能拆分為三個高內聚的組件:

  • Endpoint:網路通信
  • Processor:應用層協定解析
  • Adapter:Request/Response 轉換
flowchart LR
    subgraph ProtocolHandler
        E[Endpoint<br>TCP/IP 層<br>Socket 通信] --> P[Processor<br>HTTP/AJP 層<br>協定解析]
        P --> A[Adapter<br>ServletRequest<br>轉換]
    end
    A --> C[容器]

Endpoint 組件#

Endpoint 是對傳輸層的抽象,負責 Socket 的接收和發送。

Tomcat 支持的 I/O 模型

I/O 模型實現類說明
NIONioEndpointJava NIO,默認選項
NIO.2Nio2EndpointJava 非同步 I/O
APRAprEndpointApache 可移植執行庫,C/C++ 實現

NioEndpoint 的組件#

flowchart TD
    subgraph NioEndpoint
        N[新連線] --> A[Acceptor]
        A --> P[Poller<br>Selector]
        P --> S[SocketProcessor<br>Runnable]
        S --> E[Executor<br>線程池]
    end
組件職責
LimitLatch連線數限制器,默認最大 10000 連線
Acceptor接收新連線,執行在單獨線程
Poller本質是 Selector,檢測 I/O 事件
SocketProcessor處理請求的任務物件,實現 Runnable
Executor線程池,執行 SocketProcessor

Processor 組件#

Processor 負責應用層協定的解析,將字節流解析成 Tomcat Request 物件。

// Processor 介面的核心方法
public interface Processor {
    // 處理請求
    SocketState process(SocketWrapperBase<?> socketWrapper,
                        SocketEvent status) throws IOException;
}

不同協定有不同的實現:

  • Http11Processor:處理 HTTP/1.1
  • AjpProcessor:處理 AJP 協定
  • StreamProcessor:處理 HTTP/2

Adapter 組件#

適配器模式的經典運用

Tomcat 使用 CoyoteAdapter 將 Tomcat 內部的 Request 物件轉換為標準的 ServletRequest,實現了協定處理與容器處理的解耦。

public class CoyoteAdapter implements Adapter {

    @Override
    public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
            throws Exception {

        // 1. 創建 ServletRequest 和 ServletResponse
        Request request = connector.createRequest();
        Response response = connector.createResponse();

        // 2. 轉換 Tomcat Request 到 ServletRequest
        postParseSuccess = postParseRequest(req, request, res, response);

        // 3. 呼叫容器
        connector.getService().getContainer().getPipeline()
                .getFirst().invoke(request, response);
    }
}

容器(Container)設計#

容器的層次結構#

Engine
  ├── Host (manage.example.com)
  │     ├── Context (/admin)
  │     │     ├── Wrapper (UserServlet)
  │     │     └── Wrapper (ProductServlet)
  │     └── Context (/report)
  │           └── Wrapper (ReportServlet)
  └── Host (user.example.com)
        └── Context (/shop)
              ├── Wrapper (SearchServlet)
              └── Wrapper (OrderServlet)

為什麼設計多層容器?

這種分層架構提供了極大的靈活性:

  • 一個 Tomcat 可以執行多個虛擬主機(Host)
  • 一個虛擬主機可以部署多個 Web 應用(Context)
  • 一個 Web 應用可以有多個 Servlet(Wrapper)

請求定位 Servlet 的過程#

當請求 http://user.example.com:8080/shop/order 到達時:

1. 根據連線埠 8080 ──> 找到 Connector ──> 確定 Service 和 Engine
2. 根據域名 user.example.com ──> 找到 Host
3. 根據路徑 /shop ──> 找到 Context
4. 根據路徑 /order ──> 找到 Wrapper(OrderServlet)

這個定位過程由 Mapper 組件完成,Mapper 保存了 URL 與容器的映射關係。

Pipeline-Valve 責任鏈#

每個容器都有一個 Pipeline,Pipeline 中包含多個 Valve。請求在容器間的傳遞通過責任鏈模式實現。

flowchart TD
    subgraph EP[Engine Pipeline]
        EV1[Valve1] --> EV2[Valve2]
        EV2 --> SEV[StandardEngineValve]
    end

    subgraph HP[Host Pipeline]
        HV[Valve3] --> SHV[StandardHostValve]
    end

    subgraph CP[Context Pipeline]
        CV[Valve4] --> SCV[StandardContextValve]
    end

    subgraph WP[Wrapper Pipeline]
        SWV[StandardWrapperValve]
    end

    SEV --> HP
    SHV --> CP
    SCV --> WP
    SWV --> FC[Filter Chain]
    FC --> S[Servlet]
Valve 介面定義
public interface Valve {
    // 獲取下一個 Valve
    public Valve getNext();

    // 設置下一個 Valve
    public void setNext(Valve valve);

    // 處理請求
    public void invoke(Request request, Response response)
        throws IOException, ServletException;
}

Valve vs Filter 的區別

特性ValveFilter
層級Web 容器級別應用級別
規範Tomcat 私有Servlet 標準
作用範圍所有應用單個應用
可移植性僅 Tomcat所有 Servlet 容器

類載入機制#

類載入的需求#

Tomcat 的類載入器需要解決三個問題:

  1. Web 應用隔離:同名類在不同應用中可以共存
  2. 共享類庫:多個應用共享的 JAR 包只加載一次
  3. 容器與應用隔離:Tomcat 自身的類與應用的類隔離

類載入器層次結構#

graph TD
    B[Bootstrap ClassLoader]
    E[Extension ClassLoader]
    A[Application ClassLoader]
    C[Common ClassLoader]
    CAT[Catalina ClassLoader]
    S[Shared ClassLoader]
    W1[WebApp1 ClassLoader]
    W2[WebApp2 ClassLoader]
    W3[WebApp3 ClassLoader]

    B --> E --> A --> C
    C --> CAT
    C --> S
    S --> W1
    S --> W2
    S --> W3

    style B fill:#e3f2fd
    style C fill:#fff8e1
    style W1 fill:#e8f5e9
    style W2 fill:#e8f5e9
    style W3 fill:#e8f5e9
類載入器加載路徑說明
CommonClassLoader$CATALINA_HOME/libTomcat 和所有應用共享
CatalinaClassLoader-加載 Tomcat 自身的類
SharedClassLoader-所有 Web 應用共享
WebAppClassLoaderWEB-INF/classes, WEB-INF/lib每個應用獨立的類載入器

打破雙親委託

WebAppClassLoader 打破了 Java 的雙親委託機制,它會優先加載 Web 應用自己的類,這樣不同應用的同名類才能共存。

線程上下文類載入器#

Spring 等框架使用 Class.forName() 加載業務類時,會使用當前類的類載入器。但 Spring 是由 SharedClassLoader 加載的,無法加載 Web 應用的類。

解決方案:線程上下文類載入器

// Tomcat 在啟動 Web 應用時設置線程上下文類載入器
Thread.currentThread().setContextClassLoader(webAppClassLoader);

// Spring 獲取線程上下文類載入器來加載 Bean
ClassLoader cl = Thread.currentThread().getContextClassLoader();

生命週期管理#

Lifecycle 介面#

一鍵式啟停的秘密

所有組件都實現 Lifecycle 介面,父組件在啟動時會啟動子組件,形成級聯啟動。只需呼叫 Server 的 start(),整個 Tomcat 就啟動了。

public interface Lifecycle {
    // 生命週期方法
    void init() throws LifecycleException;
    void start() throws LifecycleException;
    void stop() throws LifecycleException;
    void destroy() throws LifecycleException;

    // 監聽器管理
    void addLifecycleListener(LifecycleListener listener);
    void removeLifecycleListener(LifecycleListener listener);
}

生命週期狀態#

stateDiagram-v2
    [*] --> NEW
    NEW --> INITIALIZING: init()
    INITIALIZING --> INITIALIZED
    INITIALIZED --> STARTING_PREP: start()
    STARTING_PREP --> STARTED
    STARTED --> STOPPED: stop()
    STOPPED --> DESTROYING: destroy()
    DESTROYING --> DESTROYED
    DESTROYED --> [*]

模板方法模式#

LifecycleBase 抽象類使用模板方法模式,定義了生命週期的骨架邏輯:

public abstract class LifecycleBase implements Lifecycle {

    @Override
    public final synchronized void init() throws LifecycleException {
        // 1. 檢查狀態合法性
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }

        // 2. 觸發 INITIALIZING 事件
        setStateInternal(LifecycleState.INITIALIZING, null, false);

        // 3. 呼叫子類實現的初始化方法
        initInternal();  // 抽象方法,由子類實現

        // 4. 觸發 INITIALIZED 事件
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    }

    // 抽象方法,子類必須實現
    protected abstract void initInternal() throws LifecycleException;
}

熱部署實現#

什麼是熱部署?#

熱部署是指在 Tomcat 執行過程中,動態部署或更新 Web 應用,而不需要重啟 Tomcat。

熱部署的實現原理#

  1. 後台線程監控:Tomcat 啟動後台線程,定期檢查 webapps 目錄和 Context 描述符的變化

  2. 資源變化檢測

    • 新增 WAR 包或目錄 → 部署新應用
    • 刪除 WAR 包或目錄 → 卸載應用
    • WAR 包或 web.xml 被修改 → 重新加載應用
  3. 類載入器重建

    • 銷毀原有的 WebAppClassLoader
    • 創建新的 WebAppClassLoader
    • 重新加載所有類
熱部署組態

在 server.xml 的 Host 元素中組態:

<Host name="localhost" appBase="webapps"
      unpackWARs="true"
      autoDeploy="true"           <!-- 自動部署新應用 -->
      deployOnStartup="true">     <!-- 啟動時部署已有應用 -->

    <!-- Context 級別的重新加載 -->
    <Context path="/myapp" docBase="myapp"
             reloadable="true">   <!-- 類文件變化時自動重載 -->
    </Context>
</Host>

生產環境慎用熱部署

熱部署可能導致以下問題:

  • 類載入器洩漏(OOM)
  • 靜態變數未清理
  • 線程未正確關閉

建議在生產環境使用滾動更新或藍綠部署。

請求處理流程總結#

flowchart TD
    A[用戶端發送 HTTP 請求] --> B[Acceptor 接受連線]
    B --> C[創建 SocketChannel]
    C --> D[註冊到 Poller Selector]
    D --> E{Poller 檢測 I/O 事件}
    E --> F[創建 SocketProcessor]
    F --> G[提交到線程池執行]
    G --> H[Processor 解析 HTTP]
    H --> I[創建 Request/Response]
    I --> J[Adapter 轉換為 ServletRequest]
    J --> K[Engine Pipeline]
    K --> L[Host Pipeline]
    L --> M[Context Pipeline]
    M --> N[Wrapper Pipeline]
    N --> O[Filter Chain]
    O --> P[Servlet.service]
    P --> Q[回應按原路回傳]
文字版流程說明
1. 用戶端發送 HTTP 請求到 Tomcat 監聯的連線埠

2. Acceptor 線程接受連線,創建 SocketChannel

3. 將 SocketChannel 註冊到 Poller 的 Selector 上

4. Poller 檢測到 I/O 事件,創建 SocketProcessor 提交到線程池

5. 線程池執行 SocketProcessor,呼叫 Processor 解析 HTTP 協定

6. Processor 創建 Tomcat Request/Response 物件

7. Adapter 將 Tomcat Request 轉為 ServletRequest

8. 呼叫 Engine 的 Pipeline,請求依次經過各容器的 Valve

9. 請求最終到達 Wrapper 的 BasicValve

10. 創建 Filter 鏈,依次呼叫 Filter

11. Filter 鏈最後呼叫 Servlet 的 service() 方法

12. 回應按原路回傳

設計模式總結#

設計模式應用場景
組合模式容器的層級結構(Container 介面)
責任鏈模式Pipeline-Valve、Filter Chain
觀察者模式生命週期事件監聯(LifecycleListener)
模板方法模式LifecycleBase 的生命週期管理
適配器模式CoyoteAdapter 轉換 Request
外觀模式RequestFacade 包裝 Request
工廠模式連線器和協定處理器的創建