Tomcat 容器設計和 servlet 處理

Tomcat 對 NIO 和 HTTP 協議的實現 一文中描述了對 TCP 連接和解析 HTTP 請求的處理,接下來會進一步封裝請求交給 Catalina 容器處理以生成響應。在此之前首先了解一下 web 應用程序部署後在容器裏的體現。

容器內部的 web 應用程序

一個 web 應用由 servlet、html頁面、類和其他資源組成,web程序的根目錄是一個特定的路徑,如 /examples,以這個前綴開始的請求都被路由到 examples 應用的 ServletContext 環境中。

默認情況下,容器啓動時會部署 webapps 文件夾下的 war 文件或者子文件夾,運行時 Engine 有一個後臺線程也會定時掃描部署,部署的本質就是解析 web.xml,創建 Context、Wrapper 等對象並組裝的過程,部署完成後的內部簡單結構:

tomcat-webapps

上圖類似於樹結構,根據請求 URI,自頂向下總能找到一個 Servlet 來處理,每個應用程序都有一個默認的 DefaultServlet 來處理靜態資源或者是沒有匹配到 Servlet 的請求。

容器內請求的處理

容器主要通過 Pipeline 以流水線外加 valve 閥門的機制聯合各組件處理請求,具體結構如下:

simple-http

默認情況下,容器處理過程爲:

  1. Connector 接收並解析 HTTP 請求,封裝成 Request 對象,交給 Adapter,然後 Adapter 通過 Mapper 對象根據請求 host 頭域和請求 URL 找到匹配的虛擬主機、web應用程序(ServletContext)以及對應的 Wrapper 對象,最後 調用 Engine 的 Pipeline 中 valves 的 invoke 方法,請求轉入容器。
  2. Engine 默認只有 StandardEngineValve,它只是簡單將請求轉入到其匹配的 Host 子容器的 Pipeline 中。
  3. Host 默認有兩個 Valve,處理過程爲:
    • 首先,由 ErrorReportValve 處理,它主要調用下一個 Valve,處理有錯誤的響應;
    • 然後,由 StandardHostValve 處理,它獲取請求匹配的應用程序 Context,將當前線程的 ClassLoader 綁定爲 Context關聯的 WebappClassLoader;
    • 最後,將請求交給 Context 的 Pipeline 處理,當 Context 處理完畢返回時,此 Valve 會還原當前線程的 ClassLoader。
  4. Context 默認只有 StandardContextValve,處理過程爲:
    • 首先,它判斷請求路徑是否包含 META-INF或者 WEB-INF,是的話直接返回404錯誤;
    • 然後,獲取 web.xml 中配置的 ServletRequestListener,觸發其初始化的方法;
    • 最後,拿到封裝 Servlet 的 Wrapper 對象,將請求轉入它的 Pipeline 處理,處理完畢銷燬 RequestListener。
  5. Wrapper 默認的閥門是 StandardWrapperValve,首先,它檢查應用程序和 Wrapper是否可用,如果可用,使用 Wrapper 的 allocate() 方法申請 Servlet 實例,有兩種情況:
    • 如果 Servlet 已經加載初始化則直接返回,沒有則使用Context關聯的 WebappClassLoader 加載並初始化,使用的都是同一個 Servlet 對象;
    • 如果 Servlet 實現了 SingleThreadedModel 接口,那麼對於每個請求線程返回不同的 Servlet 實例,默認Servlet對象池大小爲20,也就是說相同請求併發超過20,剩餘的將等待可用的Servlet;
      接下來,創建配置的 FilterChain,依次調用過濾器鏈中的每個 Filter 的 doFilter 方法,最後一個 Filter 處理完畢後,調用 Servlet.service() 方法,根據請求方法調用 doGet 或者 doPost;最後,當 service 方法返回,過濾器鏈依次返回並按需處理響應對象。

由上可以看出,核心就在於 Pipeline 和 FilterChain,上圖 Servlet 處理完畢後的返回箭頭,表示這些 Vavle和Filter 它們既能處理請求,也能處理響應,這是藉助了線程調用棧實現的,也是典型的責任鏈模式的實現,此模式的應用相當廣泛。

小結

在分析源碼的過程中發現,直接加斷點跟請求不難,難的在於一開始在腦海裏無法理清這麼多對象的關聯關係,甚至有時候明知道這個類會處理,卻不知調用點在哪。理清這些的關鍵在於理解 Tomcat 對配置文件的解析,觸發 Lifecycle 生命週期事件的時機以及合適的斷點位置。

Tomcat 源碼還是挺多的,過於深入細節反而得不償失,也歡迎提一些問題點,以此爲切入點來討論和分析源碼。接下來,會對 Tomcat 高級特性進行分析,比如集羣的實現原理、Session的處理、連接池等等,因爲我使用的是 Tomcat6 版本的源碼,後續也會分析 Tomcat7 做出的優化和重構。

其他關聯文章:
Tomcat 架構概述
Tomcat 啓動初始化和停止
Tomcat 對 NIO 和 HTTP 協議的實現
Tomcat 源碼註釋

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