jetty加載spring-context容器源碼分析

帶着疑問開始

web.xml的順序問題

先拿一個最簡單的spring mvc web.xml來講問題,以下圖:若是我將三者的順序倒置或是亂置,會產生什麼結果呢?html

啓動報錯?仍是加載未知結果?仍是毫無影響?web

結果是什麼呢?讓咱們用實踐來證實一下:go->jetty-spring-context project 現場演示spring

//todo 以後貼出結果express

applicationContext.xml和spring-mvc-servlet.xml怎麼配置

最簡單的配置(這樣不只產生兩個容器並且每一個容器都生成同樣的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.xml的初始化優先於spring-mvc-servlet.xml學習

兩個context容器究竟是怎樣的

  1. 兩個容器分別是怎麼初始化的呢?
  2. 爲何applicationContext容器就是mvc context容器的父容器呢?
  3. 這兩個容器分別是什麼類型的applicationContext實現呢?(咱們知道applicationContext是接口)

下邊的圖是applicationContext接口的少部分實現

深刻源碼解決疑難

爲何無論怎麼配置app.xml老是在mvc以前初始化

下邊讓咱們用源碼一步一步來分析其中的奧妙

啓動jetty容器入口

 ......................前邊還有一大段代碼

初始化調用的是mms server的start方法,其實server沒有start方法,是它的父類AbstractLifeCyle的start方法,而後再回調,咱們來看下server的結構

在繼續講server是怎麼一步步調用以前,咱們須要知道兩個事情

  1. ContextLoaderListener

  2. DispatcherServlet

ContextLoaderListener和DispatcherServlet的實質(插播)

contextLoaderListerner實質是實現了EventListener的一個事件監聽器

相關事件通知,在個人另一篇wiki中有詳細介紹:

事件通知機制深刻源碼#事件通知機制深刻源碼-ApplicationListener

或者 直接看這裏:Spring事件通知機制詳解

contextLoaderListerner 的方法  contextInitialized  會被回調;
DispatcherServlet實質上是一個servlet,固然,這個不用說也看的很清楚

DispatcherServlet 的父類FrameworkServlet的方法initServletBean會被回調

(警告)   這時候咱們還要知道一個事情:contextLoaderListerner 和 DispatcherServlet 是在spring的兩個package裏邊,前者在spring-web裏邊,後者在spring-webmvc裏邊,這個對後邊的理解有幫助

啓動jetty容器入口(續)

爲何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初始化context容器的過程

ContextLoaderListener結構

ContextLoaderListener入口

首先,回調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容器的主要入口

DispatcherServlet初始化context容器的過程

DispatcherServlet結構

DispatcherServlet入口

初始化webApplicationContext,

建立webApplicationContext

這裏的contextClass是這麼決定的

最終也是調用refresh實例化的

最終完成第二個容器的初始化