之前寫了幾篇關於SpringBoot
的文章《面試高頻題:springBoot自動裝配的原理你能說出來嗎》、《保姆級教程,手把手教你實現一個SpringBoot的starter》,這幾天忽然有個讀者問:能說一說Spring
的父子容器嗎?說實話這其實也是Spring
八股文裏面一個比較常見的問題。在個人印象裏面Spring
就是父容器,SpringMvc
就是子容器,子容器能夠訪問父容器的內容,父容器不能訪問子容器的東西。有點相似java裏面的繼承的味道,子類能夠繼承父類共有方法和變量,能夠訪問它們,父類不能夠訪問子類的方法和變量。在這裏就會衍生出幾個比較經典的問題:html
Spring
容器來管理?(Spring
的applicationContext.xml
中配置全局掃描)Spring-mvc
子容器裏面來管理(springmvc
的spring-servlet.xml
中配置全局掃描)?Spring
的父子容器應該問題不大了。咱們能夠看下官網提供的父子容器的圖片WebApplicationContext
實例,爲了進行區分,分別稱之爲:Servlet WebApplicationContext
(子容器)、Root WebApplicationContext
(父容器)。web
層進行配置,如控制器(controller
)、視圖解析器(view resolvers
)等相關的bean。經過spring mvc
中提供的DispatchServlet來加載配置,一般狀況下,配置文件的名稱爲spring-servlet.xml。service
層、dao
層進行配置,如業務bean
,數據源(DataSource
)等。一般狀況下,配置文件的名稱爲applicationContext.xml
。在web
應用中,其通常經過ContextLoaderListener
來加載。要想很好的理解它們之間的關係,咱們就有必要先弄清楚Spring的啓動流程。要弄清楚這個啓動流程咱們就須要搭建一個SpringMvc
項目,說句實話,用慣了SpringBooot
開箱即用,忽然在回過頭來搭建一個SpringMvc
項目還真有點不習慣,一大堆的配置文件。(雖然也能夠用註解來實現)具體怎麼搭建SpringMvc
項目這個就不介紹了,搭建好項目咱們運行起來能夠看到控制檯會輸出以下日誌:日誌裏面分別打印出了父容器和子容器分別的一個耗時。java
咱們只須要Controller
與咱們的Service
中實現ApplicationContextAware
接口,就能夠得知對應的管理容器:在Service
所屬的父容器裏面咱們能夠看到父容器對應的對象是XmlWebApplicationContext@3972
在
Controller
中對應的容器對象是XmlWebApplicationContext@4114
因而可知它們是兩個不一樣的容器。程序員
咱們知道SpringServletContainerInitializer
從 servlet 3.0
開始,Tomcat
啓動時會自動加載實現了 ServletContainerInitializer
接口的類(須要在 META-INF/services
目錄下新建配置文件)也稱爲 SPI(Service Provider Interface)
機制,SPI
的應用仍是挺廣的好比咱們的JDBC
、還有Dubbo
框架裏面都有用到,若是還有不是很瞭解SPI
機制的 能夠去學習下。因此咱們的入口就是SpringServletContainerInitializer
的onStartup
方法,這也應該是web容器啓動調用Spring
相關的第一個方法。web
若是實在找不到入口的話,咱們能夠 根據控制檯打印的日誌,而後拿着日誌進行反向查找這應該總能找到開始加載父容器的地方。啓動的時候控制檯應該會打印出「Root WebApplicationContext: initialization started
」 咱們拿着這個日誌就能定位到代碼了面試
`public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {` `if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {` `throw new IllegalStateException(` `"Cannot initialize context because there is already a root application context present - " +` `"check whether you have multiple ContextLoader* definitions in your web.xml!");` `}` `servletContext.log("Initializing Spring root WebApplicationContext");` `Log logger = LogFactory.getLog(ContextLoader.class);` `if (logger.isInfoEnabled()) {` `logger.info("Root WebApplicationContext: initialization started");` `}` `long startTime = System.currentTimeMillis();` `try {` `// Store context in local instance variable, to guarantee that` `// it is available on ServletContext shutdown.` `if (this.context == null) {` `// 經過反射去建立context` `this.context = createWebApplicationContext(servletContext);` `}` `if (this.context instanceof ConfigurableWebApplicationContext) {` `ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;` `if (!cwac.isActive()) {` `// The context has not yet been refreshed -> provide services such as` `// setting the parent context, setting the application context id, etc` `if (cwac.getParent() == null) {` `// The context instance was injected without an explicit parent ->` `// determine parent for root web application context, if any.` `ApplicationContext parent = loadParentContext(servletContext);` `cwac.setParent(parent);` `}` `// IOC容器初始化` `configureAndRefreshWebApplicationContext(cwac, servletContext);` `}` `}` `servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);` `ClassLoader ccl = Thread.currentThread().getContextClassLoader();` `if (ccl == ContextLoader.class.getClassLoader()) {` `currentContext = this.context;` `}` `else if (ccl != null) {` `currentContextPerThread.put(ccl, this.context);` `}` `if (logger.isInfoEnabled()) {` `long elapsedTime = System.currentTimeMillis() - startTime;` `logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");` `}` `return this.context;` `}` `catch (RuntimeException | Error ex) {` `logger.error("Context initialization failed", ex);` `servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);` `throw ex;` `}` `}`
這段代碼就是建立父容器的地方。spring
接着咱們再來看看建立子容器的地方:在FrameworkServlet
類上述代碼是否是會有個疑問咱們怎麼就會執行
FrameworkServlet
的initServletBean
方法。這是因爲咱們在web.xml
裏面配置了DispatcherServlet
,而後web容器就會去調用DispatcherServlet
的init
方法,而且這個方法只會被執行一次。經過init方法就會去執行到initWebApplicationContext
這個方法了,這就是web子容器的一個啓動執行順序。express
`<servlet>` `<servlet-name>dispatcher</servlet-name>` `<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>` `// 若是不配置這個load-on-startup 1 不會再項目啓動的時候執行inti方法。而是首次訪問再啓動` `<load-on-startup>1</load-on-startup>` `</servlet>`
大概流程以下:從上述代碼咱們能夠發現子容器是本身從新經過反射
new
了一個新的容器做爲子容器, 而且設置本身的父容器爲Spring
初始化建立的WebApplicationContext
。而後就是去加載咱們在web.xml
裏面配置的Springmvc
的配置文件,而後經過建立的子容器去執行refresh
方法,這個方法我相信不少人應該都比較清楚了。編程
咱們知道了Sping
父容器以及SpingMvc
子容器的一個啓動過程,以及每一個容器都分別幹了什麼事情如今再回過頭來看看上述四個問題。架構
J2EE
三層架構中,在service
層咱們通常使用spring
框架來管理, 而在web
層則有多種選擇,如spring mvc、struts
等。所以,一般對於web
層咱們會使用單獨的配置文件。例如在上面的案例中,一開始咱們使用spring-servlet.xml
來配置web層,使用applicationContext.xml來配置service
、dao
層。若是如今咱們想把web
層從spring mvc
替換成struts
,那麼只須要將spring-servlet.xml
替換成Struts
的配置文件struts.xml
便可,而applicationContext.xml
不須要改變。`<context:component-scan use-default-filters="false" base-package="cn.javajr">` `<context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />` `<context:include-filter type="annotation" expression="org.springframework.stereotype.Component" />` `<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />` `<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />` `</context:component-scan>`
而後在SpringMvc
的配置裏面不配置掃描包路徑。很顯然這種方式是行不通的,這樣會致使咱們請求接口的時候產生404
。由於在解析@ReqestMapping註解的過程當中initHandlerMethods
()函數只是對Spring MVC
容器中的bean
進行處理的,並無去查找父容器的bean
, 所以不會對父容器中含有@RequestMapping
註解的函數進行處理,更不會生成相應的handler
。因此當請求過來時找不處處理的handler
,致使404。併發
spring-servlet.xml
中這個是可行的。爲何可行由於無非就是把全部的東西所有交給子容器來管理了,子容器執行了refresh
方法,把在它的配置文件裏面的東西所有加載管理起來來了。雖然能夠這麼作不過通常應該是不推薦這麼去作的,通常人也不會這麼幹的。若是你的項目裏有用到事物、或者aop記得也須要把這部分配置須要放到Spring-mvc子容器的配置文件來,否則一部份內容在子容器和一部份內容在父容器,可能就會致使你的事物或者AOP不生效。(這裏不就有個經典的八股文嗎?你有遇到事物不起做用的時候,其實這也是一種狀況)Spring
幫咱們處理了,可是咱們仍是須要知道有這麼個東西,否則咱們有可能遇到問題的時候可能不知道如何下手。好比爲啥我這個事物不起做用了,我這個aop
怎麼也不行了,網上都是這麼配置的。站在巨人的肩膀上摘蘋果: https://www.cnblogs.com/grasp...
*推薦👍 :Java高併發編程基礎三大利器之CyclicBarrier*
推薦👍 :Java高併發編程基礎三大利器之CountDownLatch
推薦👍 :Java高併發編程基礎三大利器之Semaphore
推薦👍 :Java高併發編程基礎之AQS
推薦👍 :可惡的爬蟲直接把生產6臺機器爬掛了!