先拿一個最簡單的spring mvc web.xml來說問題,如下圖:如果我將三者的順序倒置或是亂置,會產生什麼結果呢?
啟動報錯?還是加載未知結果?還是毫無影響?
結果是什麼呢?讓我們用實踐來證明一下:go->jetty-spring-context project 現場演示
//todo 之後貼出結果
最簡單的配置(這樣不僅產生兩個容器而且每個容器都生成一樣的bean)
applicationContext:
spring-mvc-servlet.xml
正確的配置其中之一
1 applicationContext.xml 2 <?xml version="1.0" encoding="UTF-8"?> 3 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns="http://www.springframework.org/schema/beans" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd 7 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"> 8 <!--正確的配置--> 9 <context:component-scan base-package="com.meituan.jetty.spring"> 10 <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 11 </context:component-scan> 12 </beans> 13 14 spring-mvc-servlet.xml 15 <beans xmlns="http://www.springframework.org/schema/beans" 16 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 17 xmlns:context="http://www.springframework.org/schema/context" 18 xsi:schemaLocation="http://www.springframework.org/schema/beans 19 http://www.springframework.org/schema/beans/spring-beans.xsd 20 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 21 <!--正確的配置--> 22 <context:component-scan base-package="com.meituan.jetty.spring.controller"/> 23 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 24 <property name="prefix" value="/"/> 25 <property name="suffix" value=".jsp"/> 26 </bean> 27 </beans>
可以麼?go->jetty-spring-context project 現場演示問題來了,通過結果我們發現,確實有兩個容器,那麼由於兩個容器同時可以有一樣的beans,那是否可以直接去掉ApplicationContext容器呢?
結果:applicationContext.xml的初始化優先於spring-mvc-servlet.xml
下邊的圖是applicationContext接口的少部分實現
下邊讓我們用源碼一步一步來分析其中的奧妙
......................前邊還有一大段代碼
初始化調用的是mms server的start方法,其實server沒有start方法,是它的父類AbstractLifeCyle的start方法,然後再回調,我們來看下server的結構
在繼續講server是怎麼一步步調用之前,我們需要知道兩個事情
contextLoaderListerner實質是實現了EventListener的一個事件監聽器
相關事件通知,在我的另外一篇wiki中有詳細介紹:
事件通知機制深入源碼#事件通知機制深入源碼-ApplicationListener
或者 直接看這裡:Spring事件通知機制詳解
contextLoaderListerner 的方法 contextInitialized 會被回調
DispatcherServlet實質上是一個servlet,當然,這個不用說也看的很清楚
DispatcherServlet 的父類FrameworkServlet的方法initServletBean會被回調
這時候我們還要知道一個事情:contextLoaderListerner 和 DispatcherServlet 是在spring的兩個package裡邊,前者在spring-web裡邊,後者在spring-webmvc裡邊,這個對後邊的理解有幫助
為什麼listener能夠無論以哪種姿態都會優先於servlet執行呢?
要解決這個問題,我們先看下listener是在何時被回調的:
首先大概浏覽下這個圖,這裡對WebAppContext和ContextHandler大概有一個映像(當然,這個是jetty的源碼)
jetty啟動,會初始化一個WebAppContext(WebAppContext 繼承了 ServletContextHandler ,ServletContextHandler 繼承了ContextHandler ,而且他們都實現了 )對象;
最終,WebAppContext對象的startContext()方法會被實現,如下圖調用鏈:
而startContext方法又做了什麼事情呢?帶著疑問,我們走進下邊的代碼:
我們發現,ContextHandler裡邊存了一個listener的集合,而恰巧我們的 ContextLoaderListener 實例也在這個集合當中;
我們看到這裡把ContextLoaderListener和event事件傳遞給了callContextInitialized方法,所以ContextLoaderListener的contextInitialized方法最終會被調用,
到此為止,我們就解釋了ContextLoaderListener是會被合理的初始化的;
至於ContextLoaderListener初始化的詳細過程,請看這裡:淺談jetty如何初始化spring容器-ContextLoaderListener初始化context容器的過程
咦?好像有什麼不對的地方。哦,對,本來是WebAppContext的startContext方法,怎麼會跑到ContextHandler的startContext方法,看上圖,
是子類父類的關系,原來如此;
看調用鏈,再來說說 boot 本來調用server的start,為什麼會走到lifeCycle呢?
原來 server 繼承了 AbstractLifeCycle,jetty源代碼裡邊大量運用了 模板方法和類模板方法,我們開發的時候也可以學習這種設計模式,減少重復代碼,提高代碼復用率。
講了這麼多,還沒講到 為什麼 listener 總是在 servlet之前執行呢?
莫急,且聽下邊講解
如下,WebAppContext的 doStart 方法被調用,此時WebAppContext自己實現了一部分,其余直接調用父類->ContextHandler的doStart方法
(咦,不對,父類不是ServletContextHandler麼;哦,ServletContextHandler並未重寫這個方法)
接下來調用ContextHandler的doStart方法
ContextHandler再次調用子類WebAppContext的方法 startContext()
WebAppContext 首先調用 startWebApp,然後 startWebApp 再次調用父類 ServletContextHandler的 startContext方法
這裡就比較有意思了:ServletContextHandler首先調用父類,也就是ContextHandler的startContext方法,還記得父類的這個方法發生了什麼嗎?
對!父類這個方法裡邊初始化了 ContextLoadListener ,也就是初始化了所有的 事件通知 !!!
事件通知完成之後,開始調用servlet的initialize方法,初始化servlet;servlet初始化詳解:深入淺出jetty初始化spring容器-DispatcherServlet初始化context容器的過程
也就是說:frameworkServlet初始化方法的回調是由ServletContextHandler的startContext方法引起的!!!
看下邊 listener和servlet的執行順序:
至此為止,我們剖析了 jetty初始化 為什麼 listener的執行一定會先於servlet!!!
首先,回調ContextLoaderListener的contextInitialized方法
然後調用父類ContextLoader的contextInitialized方法,第一次初始化的時候 org.springframework.web.context.WebApplicationContext.ROOT == null
緊接著在1的時候創建context
我們看看 context 到底是怎麼創建的?創建的是哪種類型?紅框決定了創建哪種類型的 applicationContext
如下圖,通過strategy決定創建哪種類型
strategy又是怎麼初始化的呢?
look
可見,是這個配置文件決定了,wepApplicationContext的類型是XmlWebApplicationContext,接下來是configAndRefresh
最終調用 AbstractApplicationContext的refresh方法,根據配置文件內容,開始初始化;
AbstractApplicationContext的refresh的初始化都知道吧?不知道的話,可以看我這篇關於spring初始化順序的文章:Spring Init&Destroy#spring容器的主要入口
初始化webApplicationContext,
創建webApplicationContext
這裡的contextClass是這麼決定的
最終也是調用refresh實例化的
最終完成第二個容器的初始化