Tomcat 是最流行的 Java Web 容器,它的設計非常經典,運用了大量設計模式和 Java 高級技術。深入理解 Tomcat 架構,不僅能幫助我們更好地使用和調校 Tomcat,還能從中學習系統設計的精髓。
整體架構#
兩大核心組件#
Tomcat 的核心功能
- 處理 Socket 連線,負責網路字節流與 Request/Response 物件的轉換
- 加載和管理 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組件層次關係#
| 組件 | 作用 | 數量關係 |
|---|---|---|
| Server | Tomcat 實體 | 一個 Tomcat 程序對應一個 Server |
| Service | 服務組件,包裝連線器和容器 | 一個 Server 可以有多個 Service |
| Connector | 連線器,處理網路通信 | 一個 Service 可以有多個 Connector |
| Engine | 引擎,管理虛擬主機 | 一個 Service 只有一個 Engine |
| Host | 虛擬主機 | 一個 Engine 可以有多個 Host |
| Context | Web 應用 | 一個 Host 可以有多個 Context |
| Wrapper | Servlet 包裝器 | 一個 Context 可以有多個 Wrapper |
連線器(Connector)設計#
連線器的職責#
連線器負責對外交流,主要完成以下工作:
- 監聯網路連線埠
- 接受網路連線請求
- 讀取網路請求字節流
- 根據應用層協定(HTTP/AJP)解析字節流
- 生成 Tomcat Request 物件
- 將 Tomcat Request 轉成 ServletRequest
- 呼叫 Servlet 容器處理請求
- 將回應轉換回網路字節流
三大核心組件#
高內聚、低耦合的設計
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 模型 | 實現類 | 說明 |
|---|---|---|
| NIO | NioEndpoint | Java NIO,默認選項 |
| NIO.2 | Nio2Endpoint | Java 非同步 I/O |
| APR | AprEndpoint | Apache 可移植執行庫,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.1AjpProcessor:處理 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 的區別
特性 Valve Filter 層級 Web 容器級別 應用級別 規範 Tomcat 私有 Servlet 標準 作用範圍 所有應用 單個應用 可移植性 僅 Tomcat 所有 Servlet 容器
類載入機制#
類載入的需求#
Tomcat 的類載入器需要解決三個問題:
- Web 應用隔離:同名類在不同應用中可以共存
- 共享類庫:多個應用共享的 JAR 包只加載一次
- 容器與應用隔離: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/lib | Tomcat 和所有應用共享 |
| CatalinaClassLoader | - | 加載 Tomcat 自身的類 |
| SharedClassLoader | - | 所有 Web 應用共享 |
| WebAppClassLoader | WEB-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。
熱部署的實現原理#
後台線程監控:Tomcat 啟動後台線程,定期檢查 webapps 目錄和 Context 描述符的變化
資源變化檢測:
- 新增 WAR 包或目錄 → 部署新應用
- 刪除 WAR 包或目錄 → 卸載應用
- WAR 包或 web.xml 被修改 → 重新加載應用
類載入器重建:
- 銷毀原有的 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 |
| 工廠模式 | 連線器和協定處理器的創建 |