web.xml 中 load-on-startup 引發的思考

付學良發表於2013-07-27

近期工作需要把公司不少專案把構建方式轉移到maven上來,工作量還真不小,遇到各種問題,還算開心的事情就是每個問題都能解決,今天又遇到一問題,深究下來事情還不少,值得說道下。

個人習慣在maven專案使用了jetty的外掛來執行專案,就不要問我為啥非使用jetty了,tomcat也可以沒啥特殊愛好。專案改造好以後我又熟練的在IntelliJ中執行 jetty:run ,這次事情不太妙,提示以下錯誤:

DWR can't find a spring config. See following info logs for solutions
- Option 1. In dwr.xml, <create creator='spring' ...> add <param name='location1' value='beans.xml'/> for each spring config file.
- Option 2. Use a spring org.springframework.web.context.ContextLoaderListener.
- Option 3. Call SpringCreator.setOverrideBeanFactory() from your web-app

最後錯誤的堆疊可以定位到 SpringCreator 這個類上,其實這個問題大家搜下大部分文章都是告訴你新增 Spring的一個 ContextLoadListener 到 web.xml中即可。公司的框架沒使用這個東西,載入容器的部分是自己實現的。最後根據堆疊可以得出的資訊就是沒拿到 Spring 的 BeanFactory ,也就是說容器沒載入對。

之所以加 ContextLoadListener 能解決問題,是因為這樣 Spring 的容器載入優先於 dwr 的 servlet配置,所以就沒問題,我這裡載入容器的類放在了一個 servlet 的init-class的配置中了。那問題到這裡就可以斷定肯定是 servlet 載入順序出了問題,才導致了這個報錯。

這個問題牽扯到了 web.xml 中 servlet 的執行順序,這也是我之所以想把這事拿出來說的原因之一吧,大家可以在百度或者 Google 去搜尋類似的中文文章,基本有兩類:

  • 告訴你順序是 listener >> filter >> servlet ,這個好似對我們這個問題沒幫助,如果使用了 listener 就沒這事了
  • 告訴你 servlet 的執行順序,這個我開始還看到了兩種說法,一個是依賴於 load-on-startup 的配置(這個是合理的),一個是依賴於 servlet-mapping 的書寫順序(這個說法有點坑爹吧?你以為這個是 filter-mapping 啊)

這樣基本大體思路有了,就看專案中 load-on-startup 的配置就可以了,看了下還真有點問題,本來一定要第一位置執行的 servlet 的值是1 ,dwr 的值是 -1 ,這個時候你肯定要去上網看看這個順序是怎麼回事了,我又搜了點比較坑爹的資料:

當是一個負數時或者沒有指定時,則指示容器在該servlet被選擇時才載入

這個解釋有點太坑爹了,估計還真有不少人是因為這句話而選擇的 -1 ,我問了下同事當時做的這個選擇還真是這個原因,有時候中文資料害死人啊,尼瑪。

由於這個配置是 web.xml 的,所以這個時候第一反應應該是看 JSR 是否有定義這個玩意,看了下 JSR154-Servlet 2.4 還真有,人家是這樣描述的:

The element load-on-startup indicates that this servlet should be loaded (instantiated and have its init() called) on the startup of the Web application. The element content of this element must be an integer indicating the order in which the servlet should be loaded. If the value is a negative integer, or the element is not present, the container is free to load the servlet whenever it chooses. If the value is a positive integer or 0, the container must load and initialize the servlet as the application is deployed. The container must guarantee that servlets marked with lower integers are loaded before servlets marked with higher integers. The container may choose the order of loading of servlets with the same load-on-startup value.

因為載入的時候需要根據這個值來排序,所以這個節點的值得是integer。如果是負值或者沒指定的話容器可以自由實現。否則就應該按照值的大小,由小到大排列,如果值相等的話,容器也可以選擇自己的排序策略去載入。

看到這個一下子就明白了我的程式為啥出問題了。肯定是因為 -1 ,人家 jetty 自己的策略和其他的不太一樣導致的,解決問題就簡單了,既然你有個 servlet 需要在第一位,那就把配置的值調整為0,其他的 servlet 都一律調整為大於0的即可。

題外話

既然每個容器實現的策略可能不一致,很多程式設計師這個時候都忍不住看下每個容器的實現方式了,我就屬於沒忍住的一個。

Jetty

先看 jetty 的,我這裡看的是 maven 的 jetty 外掛版本為 9.0.1-SNAPSHOT 的程式碼。首先對所有servlet進行 Array.sort(servlets),看到這個基本可以猜測這個物件實現了 compareTo 的方法了,下面是程式碼:

/* ------------------------------------------------------------ */
/** Comparitor by init order.
 */
@Override
public int compareTo(ServletHolder sh)
{
    if (sh==this)
        return 0;
    if (sh._initOrder<_initOrder)
        return 1;
    if (sh._initOrder>_initOrder)
        return -1;

    int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
    if (c==0)
        c=_name.compareTo(sh._name);
    if (c==0)
        c=this.hashCode()>sh.hashCode()?1:-1;
        return c;
}

先根據數字大小來排列,如果在相等的情況下,就再對比 className,name,最後還尼瑪比不出來就比 hashCode。 相等的情況就是JSR規定的自己的排序策略了,還有一個就是在負值的時候,策略也很簡單,就是看數字大小對比了,這個和接下來的 Tomcat 就完全不一樣了。

Tomcat

Tomcat 的方式就直接先來個程式碼展示下對負值的處理方式:

org.apache.catalina.core.StandardWrapper
/**
 * Return the load-on-startup order value (negative value means
 * load on first call).
 */
public int getLoadOnStartup() {

    if (isJspServlet && loadOnStartup < 0) {
        /*
         * JspServlet must always be preloaded, because its instance is
         * used during registerJMX (when registering the JSP
         * monitoring mbean)
         */
         return Integer.MAX_VALUE;  //這個值為0x7fffffff
    } else {
        return (this.loadOnStartup);
    }
}

這個地方足矣說明和 jetty 的不同了,對相等的情況的處理和 jetty 也不是一樣的實現,Tomcat 的實現就是把每個有相同值的放到一個 ArrayList 裡面,然後把每個 list 放入到 TreeMap 中,最後兩個 while 遍歷就可以了,對相同情況的處理應該基本就是按照宣告的情況了,本身這個實現無關痛癢的,畢竟你宣告的一樣的值,其實誰先誰後本身對你的設計來說就是無關痛癢的。

WebLogic

WebLogic由於是不是開源的專案,程式碼肯定是看不了了,只能從官方文件來了解了,參照WebLogic 8.1,其中如下描述:

WebLogic Server initializes this servlet when WebLogic Server starts up. The optional content of this element must be a positive integer indicating the order in which the servlet should be loaded. Lower integers are loaded before higher integers. If no value is specified, or if the value specified is not a positive integer, WebLogic Server can load the servlet in any order during application startup.

主要就是當負值的時候可以在應用啟動的時候以任意順序來載入每個servlet。

最後

這個問題其實說來說起也不算個什麼大問題,並且據我所知除了在瀏覽器方面的標準執行的極其不理想外,其他的地方都基本按照規範來做的,JSR相對來說還是一個很靠譜的參考資料,當你有困惑的時候還是參照JSR的解釋來解決問題比較好。這個問題解決起來很簡單,就是你必須要有順序,就都設定0或者大於0的數來作為順序就可以了,暫時來說應該是沒啥相容問題的。

本來其實是個挺簡單的事,但是由於期間我好奇搜過一點中文文件,的確給我造成了極大的困擾,不知道是否還有其他人被這些人給困擾過沒。在出現的問題的時候還是建議使用英文關鍵詞並且使用google,英語會的不需要太多,只需要能看懂基本文章,並且會拼湊幾個單詞搜就足夠了,大部分程式設計師這方面估計都沒太多問題,趁早拋棄中文文件吧。

相關文章