先拿一個最簡單的spring mvc web.xml來講問題,以下圖:若是我將三者的順序倒置或是亂置,會產生什麼結果呢?html
啓動報錯?仍是加載未知結果?仍是毫無影響?web
結果是什麼呢?讓咱們用實踐來證實一下:go->jetty-spring-context project 現場演示spring
//todo 以後貼出結果express
最簡單的配置(這樣不只產生兩個容器並且每一個容器都生成同樣的bean)設計模式
applicationContext:spring-mvc
spring-mvc-servlet.xmlmvc
正確的配置其中之一app
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容器呢?jsp
結果:applicationContext.xml的初始化優先於spring-mvc-servlet.xml學習
下邊的圖是applicationContext接口的少部分實現
下邊讓咱們用源碼一步一步來分析其中的奧妙
......................前邊還有一大段代碼
初始化調用的是mms server的start方法,其實server沒有start方法,是它的父類AbstractLifeCyle的start方法,而後再回調,咱們來看下server的結構
在繼續講server是怎麼一步步調用以前,咱們須要知道兩個事情
ContextLoaderListener
DispatcherServlet
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實例化的
最終完成第二個容器的初始化