Tomcat 容器的設計和實現

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 類與容器提供的運行時環境之間的契約。容器和應用對相關類和接口的實現情況如下:

Java Servlet API

容器實現的類或接口的作用如下:

  • ServletContext: 表示一個 Web 應用程序,是 Servlet 運行的上下文,定義了一組與容器通信的方法
  • ServletRequest: 封裝客戶端請求,容器會創建一個它的實例,並傳給 serlvet 的 service 方法
  • ServletResponse: 封裝服務端響應,容器會創建一個它的實例,並傳給 serlvet 的 service 方法
  • FilterChain: 過濾器調用鏈視圖,控制過濾器的調用
  • RequestDispatcher: 轉發請求,將請求轉發給 JSP 或另一個 Servlet
  • HttpSession: 用戶會話,一個與 cookie 關聯

2. Tomcat 引擎設計

Tomcat 在內部抽象出了容器組件的概念,基本結構如下:

servlet-engine

容器可以執行接收的請求,並返回響應。容器之間是一種一對多的包含關係,在運行時,它們通過內部的 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):

Tomcat 容器類圖

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 的設計。接下來會對各個組件的實現進行分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章