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


帶着疑問開始

web.xml的順序問題

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

啟動報錯?還是加載未知結果?還是毫無影響?

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

//todo 之后貼出結果

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

最簡單的配置(這樣不僅產生兩個容器而且每個容器都生成一樣的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.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實例化的

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM