Tomcat 容器是對 Servlet 規范的實現,也稱為 Servlet 引擎。在分析 Tomcat 容器的設計和實現之前,首先簡單了解一下 Servlet 規范,弄清楚 Tomcat 究竟要實現什么?
1. Servlet 規范簡述
Servlet 是什么?javadoc 中已經明確說明:
Servlet 是在 Web 服務器中運行的 Java 程序,通常用於接收並響應來自 Web 客戶端的 HTTP 請求。
Servlet 提供了更高級的抽象,讓研發專注於如何處理請求和響應,而不必關心底層網絡的處理。像連接處理、解析 HTTP 等都由容器實現,那又是誰決定了容器應該做什么?
答案是 Servlet 規范,簡單來說它主要指導解決以下問題:
- 容器如何管理 Servlet 的生命周期
- 容器怎么表示請求和響應,怎么映射請求到 Servlet,怎么執行 Servlet
- 一個 Web 應用的目錄組織方式以及容器如何部署一個 Web 應用
- 其他組件的行為,如過濾器-Filter,監聽器-Listener,會話-Session
- 容器安全性問題,如授權、認證以及類隔離
javax.servlet 包中的類和接口,描述並定義了 Servlet 類與容器提供的運行時環境之間的契約。容器和應用對相關類和接口的實現情況如下:
容器實現的類或接口的作用如下:
- ServletContext: 表示一個 Web 應用程序,是 Servlet 運行的上下文,定義了一組與容器通信的方法
- ServletRequest: 封裝客戶端請求,容器會創建一個它的實例,並傳給 serlvet 的 service 方法
- ServletResponse: 封裝服務端響應,容器會創建一個它的實例,並傳給 serlvet 的 service 方法
- FilterChain: 過濾器調用鏈視圖,控制過濾器的調用
- RequestDispatcher: 轉發請求,將請求轉發給 JSP 或另一個 Servlet
- HttpSession: 用戶會話,一個與 cookie 關聯
2. Tomcat 引擎設計
Tomcat 在內部抽象出了容器和組件的概念,基本結構如下:
容器可以執行接收的請求,並返回響應。容器之間是一種一對多的包含關系,在運行時,它們通過內部的 pipeline(管道) 串聯起來。容器主要有以下幾種:
- Engine - 頂級容器,不能被其他容器包含,它接受處理連接器的所有請求,並將響應返回相應的連接器,子容器通常是 Host 或 Context
- Host - 類似 Apache 虛擬主機的概念,包含主機名稱和IP地址,這里默認是localhost,父容器是 Engine,子容器是 Context
- Context - 表示一個 Web 應用程序,是 Servlet、Filter 的父容器
- Wrapper - 表示一個 Servlet,它負責管理 Servlet 的生命周期,並提供了方便的機制使用攔截器,沒有子容器
容器可以和多個組件關聯,組件主要提供通用或定制的功能:
- Loader - 類加載器,用於在運行時加載類到容器
- Manager - 管理 Session 池
- Realm - 安全域的只讀接口,用於驗證用戶身份及其相應角色
- Vavle - 與特定容器關聯的請求處理組件,由容器的 Pipeline 管理,取義於閥門在現實世界的管道中用來控制或修改流量
- Listener - 這里只是一個統稱,主要想強調容器內部設計了很多事件,比如組件的生命周期事件,容器屬性變動的事件等
Servlet 引擎就是由容器和組件組合嵌套而成,實現時設計的類圖及類的核心方法如下(Tomcat 版本 6.0.53):
3. 容器生命周期的設計
容器的大部分實現依賴於 Lifecycle(如啟動、配置),Lifecycle 是觀察者模式的應用,Tomcat 使用 LifecycleSupport 類用於周期事件的添加、刪除和觸發。運用 Lifecycle 在實現時大量使用接口而不是具體的類,更加靈活。
Tomcat 設計了 init、start、stop、destroy和periodic 五類事件,其中 periodic 是一個定時觸發事件,由每個容器所屬的后台線程觸發。容器在初始化時會默認添加一個用於配置的 Listener,如上圖所示,類的作用如下:
- EngineConfig: 無具體功能
- HostConfig: 主要負責創建 Context 實例;檢查已部署應用資源是否變化並重新部署
- ContextConfig: 主要負責解析 web.xml 初始化 Servlet、Filter、Listener,添加資源監控目錄,供 Loader 熱加載
4. Pipeline 管道的設計 - 執行 Servlet
Pipeline 是一個很常用的處理模型,和 FilterChain 大同小異,都是責任鏈模式的實現,Pipeline 內部有多個 Valve,這些 Valve 因棧的特性都有機會處理請求和響應。
Tomcat 的實現類是 StandardPipeline,內部的 Valve 是一個有固定尾節點的單鏈表,插入時采用頭插法逆序插入,讀取時使用頭結點順序讀取。
這里主要對每個容器的固定 valve 的功能進行分析,容器也是通過最后一個 valve 串聯起來:
- StandardEngineValve: 根據請求的服務器名稱,選擇適當的 Host 來處理此請求
- StandardHostValve: 主要功能如下:
- 匹配 Context,綁定當前 Web 應用的類加載器到當前線程,進入 Context 管道處理請求
- 響應返回時,如果有異常,生成錯誤頁面,並還原線程的類加載器
- StandardContextValve: 主要功能如下:
- 禁止直接訪問 WEB-INF 和 META-INF目錄
- 若進行了熱加載,重新關聯類加載器
- 獲取匹配的 Wrapper 和配置的 ServletRequestListener,進入 Wrapper 管道處理請求
- 響應返回時,銷毀之前初始化的 ServletRequestListener
- StandardWrapperValve: 主要功能如下:
- 反射加載一個 Servlet 實例,並創建一個過濾器鏈
- 調用配置的過濾器,然后調用 servlet 的 service() 方法
- 最后釋放過濾器鏈和 serlvet 實例
- 如果 servlet 被標記不可用,調用它的 destroy() 方法
6. Web 應用程序的部署
部署的過程其實就是解析 xml 實例化對象,並觸發和處理容器及組件對應生命周期事件的過程。
在 Tomcat 中,一個 Context 實例就代表一個 Web 應用程序,所以部署的第一步就是創建一個 StandardContext 對象。在創建時 HostConfig 首先會查找 Context 描述符,它可能在兩個位置:
- $CATALINA_BASE/conf/<engine>/<host>/[webappname].xml
- $CATALINA_BASE/webapps/webappname/META_INF/context.xml
如果兩個位置都不存在此文件,則使用 conf/context.xml 默認配置。
Context 實例化后會觸發 init 和 start 生命周期事件:
- init - 會創建用於解析 context.xml 和 web.xml 的工具 Digester 的實例,並解析context.xml
- start - 則會根據 web.xml 部署描述符實例化 Servlet、Filter、Listener 等 Web 組件
6.1 熱部署和熱加載
熱部署和熱加載的操作都是由容器的后台線程調用自身、子容器或組件的 backgroundProcess() 方法或 periodic 生命周期事件觸發。
如果 Host 的 autoDeploy 屬性為 true,那么在運行時將 Web 應用程序放到 webapps 目錄下,會自動部署;此外它還監視 .war、context.xml 和 web.xml 以及docBase 目錄下的靜態資源文件,如果 lastModified 有變動會重新部署。
如果 Context 的 reloadable 屬性為 true,那么 Loader 組件的 backgroundProcess() 方法會檢測 class和jar 的變化並自動加載。
7. 小結
本文主要對容器的設計進行了分析,其中核心就是 Lifecycle 和 Pipeline 的設計。接下來會對各個組件的實現進行分析。