前一篇文章講了org.apache.catalina.startup.HostConfig
的 lifecycleEvent 方法中所做的事情。最後看到在 Tomcat 啟動時或啟動後(後臺執行緒定時掃描)會呼叫 HostConfig 類的 deployApps 方法:
可以看到這裡部署應用有三種方式:XML 檔案描述符、WAR 包、檔案目錄。三種方式部署的總體流程很相似,都是一個 web 應用分配一個執行緒來處理,這裡統一放到與 Host 內部的執行緒池物件中( startStopExecutor ),所以有時會看到在預設配置下 Tomcat 啟動後可能有一個叫-startStop-
的執行緒還會執行一段時間才結束。但瀏覽這三種部署方式的實現程式碼,裡面都是構建一個 Context 物件,並將構建好的 Context 物件與 Host 元件關聯起來(即呼叫host.addChild(context)
這句,具體程式碼在 HostConfig 類的deployDescriptor(ContextName cn, File contextXml)
、deployDirectory(ContextName cn, File dir)
、deployWAR(ContextName cn, File war)
三個方法中,這裡不再貼出程式碼來詳細分析)。
前一篇文章只分析到這步,可以看出與一個 web 應用相對應的一個 Context 物件已經構建出來了,但如果容器只執行到這裡根本無法響應一個瀏覽器的一次請求。就 web 伺服器的實現來看一次請求過來除了需要根據內部 Context 構建找到這次請求訪問的web應用具體所對應的 Context 物件,還需要包含 web 應用中具體的哪個 Servlet 來處理這次請求,中間是否還需要執行相應的過濾器( filter )、監聽器( listener )等,做過 java 的 web 開發的同學都知道,這些資訊是配置在一個 web 應用的WEB-INFweb.xml
檔案的(servlet3 中已經支援將這些配置資訊放到 Java 檔案的註解中,但萬變不離其宗,總歸要在 web 應用的某個地方說明,並在容器啟動時載入,這樣才能真正提供 web 服務,響應請求)。
看到這裡可以猜到 Tomcat 容器載入 web 應用時必定會有對於每個應用的 web.xml 檔案的解析過程,本文就來看看這個解析過程。
在本文開頭提到的三種部署應用的實現程式碼中有一些共通的程式碼,這裡摘出來說明一下:
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
context.addLifecycleListener(listener);
複製程式碼
host.addChild(context);
複製程式碼
第一段是在所有 Context 物件構建時會新增一個監聽器,這裡監聽器的類名是 StandardHost 類的例項變數 configClass ,其預設值就是org.apache.catalina.startup.ContextConfig
。第二段是將當前構建的 Context 物件新增到父容器 Host 物件中。
先看 StandardHost 的 addChild 方法的實現:
可以看到這段程式碼最後呼叫了父類的 addChild 方法:
這裡看下 addChildInternal 方法的實現:
可以看到會呼叫子容器的 start 方法,就是指呼叫 StandardContext 的 start 方法。
即給 host 物件新增子容器時將會呼叫子容器的 start 方法,按照前面文章的分析,呼叫 StandardContext 的 start 方法最終會呼叫org.apache.catalina.core.StandardContext
類的 startInternal 方法(該方法程式碼較長,建議自己閱讀,不再貼出),這裡將會發布一系列事件,按呼叫前後順序這些事件包括:BEFORE_INIT_EVENT
、AFTER_INIT_EVENT
、BEFORE_START_EVENT
、CONFIGURE_START_EVENT
、START_EVENT
、AFTER_START_EVENT
。
前面提到在構建 Context 物件時都會註冊一個監聽器org.apache.catalina.startup.ContextConfig
,看下這個類的 lifecycleEvent 方法中(為什麼會執行這個方法可以看這篇文章的分析)監聽了哪些事件:
與 Context 的 start 方法呼叫相關的事件監聽前後順序為:AFTER_INIT_EVENT
(執行 init 方法)、BEFORE_START_EVENT
(執行 beforeStart 方法)、CONFIGURE_START_EVENT
(執行 configureStart 方法)。
在 configureStart 方法將直接呼叫 webConfig 方法,正是在這個方法中將會解析 web.xml 檔案:
這個方法裡面做的事情,在英文註釋中說的很清楚了,概括起來包括合併 Tomcat 全域性 web.xml 、當前應用中的 web.xml 、web-fragment.xml 和 web 應用的註解中的配置資訊,並將解析出的各種配置資訊(如 servlet 配置、filter 配置等)關聯到 Context 物件中(在上面的程式碼第 140 行:webXml.configureContext(context)
)。
看下 configureContext 方法:
可以看到裡面對 context 呼叫了各種 set、add 方法,從而將 web.xml 中的各種配置資訊與表示一個 web 應用的 context 物件關聯起來。