一、背景
最近在專案中遇到了啟動時出現載入service註解注入失敗的問題,後來經過不懈努力發現了是因為web.xml配置檔案中的元素載入順序導致的,那麼就抽空研究了以下tomcat在啟動時web.xml檔案中元素的載入順序,現在和大家分享。
二、問題剖析和研究結果
遇到這種問題的時候,一般看原始碼是最直接和最權威的獲取答案的方式,根據tomcat架構設計Context的實現類是StandardContext,全稱org.apache.catalina.core.StandardContext。看到其實現Lifecycle介面,我們在StandardContext中找到startInternal方法,下面給出我把暫時無用的程式碼去掉後的註釋版原始碼:
1 /** 2 * Start this component and implement the requirements 3 * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. 4 * 5 * @exception LifecycleException if this component detects a fatal error 6 * that prevents this component from being used 7 */ 8 @Override 9 protectedsynchronized void startInternal() throwsLifecycleException { 10 //設定webappLoader 程式碼省略 11 12 // Standard container startup 程式碼省略 13 14 try{ 15 16 // Set up the context init params 17 //初始化context-param節點資料 18 mergeParameters(); 19 20 21 // Configure and call application event listeners 22 //配置和呼叫應用程式事件listeners 23 if(ok) { 24 if(!listenerStart()) { 25 log.error("Error listenerStart"); 26 ok = false; 27 } 28 } 29 30 // Configure and call application filters 31 //配置和呼叫應用程式filters 32 if(ok) { 33 if(!filterStart()) { 34 log.error("Error filterStart"); 35 ok = false; 36 } 37 } 38 39 // Load and initialize all "load on startup" servlets 40 //載入和初始化配置在load on startup的servlets 41 if(ok) { 42 loadOnStartup(findChildren()); 43 } 44 45 // Start ContainerBackgroundProcessor thread 46 super.threadStart(); 47 }finally{ 48 // Unbinding thread 49 unbindThread(oldCCL); 50 } 51 52 }
那我們接著歸納和整理一下程式碼:
1.首先初始化context-param節點
2.接著配置和呼叫listeners 並開始監聽
3.然後配置和呼叫filters filters開始起作用
4.最後載入和初始化配置在load on startup的servlets
即元素載入順序為:
context-param --> listeners --> filters --> servlets
注意:
1.該載入順序並不會受元素在web.xml檔案中的位置的影響。
2.但對於某類配置節而言,與它們出現的順序是有關的。以 filter 為例,web.xml 中當然可以定義多個 filter,與 filter 相關的一個配置節是 filter-mapping,這裡一定要注意,對於擁有相同 filter-name 的 filter 和 filter-mapping 配置節而言,filter-mapping 必須出現在 filter 之後,否則當解析到 filter-mapping 時,它所對應的 filter-name 還未定義。web 容器啟動時初始化每個 filter 時,是按照 filter 配置節出現的順序來初始化的,當請求資源匹配多個 filter-mapping 時,filter 攔截資源是按照 filter-mapping 配置節出現的順序來依次呼叫 doFilter() 方法的。
接著讓我們來回憶一下web專案的啟動順序
1.web容器讀取web.xml配置檔案,並首先讀取<context-param>和<listener>兩個結點。
2.容器建立一個ServletContext(servlet上下文),該web專案的所有部分都將共享這個上下文。
3.容器將<context-param>轉換為鍵值對,並交給servletContext。
4.容器按照load on startup中的啟動順序建立<listener>中的類例項,建立監聽器。
關於load on startup
load-on-startup 元素在web應用啟動的時候指定了servlet被載入的順序,它的值必須是一個整數。
如果它的值是一個負整數或是這個元素不存在,那麼容器會在該servlet被呼叫的時候,載入這個servlet 。
如果值是正整數或零,容器在配置的時候就載入並初始化這個servlet,容器必須保證值小的先被載入。如果值相等,容器可以自動選擇先載入誰。
正數的值越小,啟動該servlet的優先順序越高。
三、總結
通過研究原始碼我們明白了web.xml中各個元素的載入順序,再遇到這種問題,我們就可以很快的定位出問題所在了。由此也發現和體會到了研究原始碼是一種很好的習慣也是解決問題不可缺少的方式。