一、背景
最近在項目中遇到了啟動時出現加載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中各個元素的加載順序,再遇到這種問題,我們就可以很快的定位出問題所在了。由此也發現和體會到了研究源碼是一種很好的習慣也是解決問題不可缺少的方式。