<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試高頻題:Spring和SpringMvc父子容器你能說清楚嗎

          共 6868字,需瀏覽 14分鐘

           ·

          2021-12-30 04:41

          點擊上方“Java金融”,選擇“設(shè)為星標”

          后臺回復"888"獲取bat面試題集

          引言

          以前寫了幾篇關(guān)于SpringBoot的文章《面試高頻題:springBoot自動裝配的原理你能說出來嗎》《保姆級教程,手把手教你實現(xiàn)一個SpringBoot的starter》,這幾天突然有個讀者問:能說一說Spring的父子容器嗎?說實話這其實也是Spring八股文里面一個比較常見的問題。在我的印象里面Spring就是父容器,SpringMvc就是子容器,子容器可以訪問父容器的內(nèi)容,父容器不能訪問子容器的東西。有點類似java里面的繼承的味道,子類可以繼承父類共有方法和變量,可以訪問它們,父類不可以訪問子類的方法和變量。在這里就會衍生出幾個比較經(jīng)典的問題:

          • 為什么需要父子容器?
          • 是否可以把所有類都通過Spring容器來管理?(SpringapplicationContext.xml中配置全局掃描)
          • 是否可以把我們所需的類都放入Spring-mvc子容器里面來管理(springmvcspring-servlet.xml中配置全局掃描)?
          • 同時通過兩個容器同時來管理所有的類?如果能夠把上面這四個問題可以說個所以然來,個人覺得Spring的父子容器應(yīng)該問題不大了。我們可以看下官網(wǎng)提供的父子容器的圖片上圖中顯示了2個WebApplicationContext實例,為了進行區(qū)分,分別稱之為:Servlet WebApplicationContext(子容器)、Root WebApplicationContext(父容器)。
          • Servlet WebApplicationContext:這是對J2EE三層架構(gòu)中的web層進行配置,如控制器(controller)、視圖解析器(view resolvers)等相關(guān)的bean。通過spring mvc中提供的DispatchServlet來加載配置,通常情況下,配置文件的名稱為spring-servlet.xml。
          • Root WebApplicationContext:這是對J2EE三層架構(gòu)中的service層、dao層進行配置,如業(yè)務(wù)bean,數(shù)據(jù)源(DataSource)等。通常情況下,配置文件的名稱為applicationContext.xml。在web應(yīng)用中,其一般通過ContextLoaderListener來加載。

          Spring的啟動

          要想很好的理解它們之間的關(guān)系,我們就有必要先弄清楚Spring的啟動流程。要弄清楚這個啟動流程我們就需要搭建一個SpringMvc項目,說句實話,用慣了SpringBooot開箱即用,突然在回過頭來搭建一個SpringMvc項目還真有點不習慣,一大堆的配置文件。(雖然也可以用注解來實現(xiàn))具體怎么搭建SpringMvc項目這個就不介紹了,搭建好項目我們運行起來可以看到控制臺會輸出如下日志:日志里面分別打印出了父容器和子容器分別的一個耗時。

          如何驗證是有兩個容器?

          我們只需要Controller與我們的Service中實現(xiàn)ApplicationContextAware接口,就可以得知對應(yīng)的管理容器:在Service所屬的父容器里面我們可以看到父容器對應(yīng)的對象是XmlWebApplicationContext@3972Controller中對應(yīng)的容器對象是XmlWebApplicationContext@4114由此可見它們是兩個不同的容器。

          源碼分析

          我們知道SpringServletContainerInitializerservlet 3.0 開始,Tomcat 啟動時會自動加載實現(xiàn)了 ServletContainerInitializer
          接口的類(需要在 META-INF/services 目錄下新建配置文件)也稱為 SPI(Service Provider Interface) 機制,SPI的應(yīng)用還是挺廣的比如我們的JDBC、還有Dubbo框架里面都有用到,如果還有不是很了解SPI機制的 可以去學習下。所以我們的入口就是SpringServletContainerInitializeronStartup方法,這也應(yīng)該是web容器啟動調(diào)用Spring相關(guān)的第一個方法。

          初始化SpringIoc

          如果實在找不到入口的話,我們可以 根據(jù)控制臺打印的日志,然后拿著日志進行反向查找這應(yīng)該總能找到開始加載父容器的地方。啟動的時候控制臺應(yīng)該會打印出“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)?{
          ????//?通過反射去創(chuàng)建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;
          ??}
          ?}

          這段代碼就是創(chuàng)建父容器的地方。

          初始化 Spring MVC

          接著我們再來看看創(chuàng)建子容器的地方:在FrameworkServlet上述代碼是不是會有個疑問我們怎么就會執(zhí)行FrameworkServletinitServletBean方法。這是由于我們在web.xml 里面配置了DispatcherServlet,然后web容器就會去調(diào)用DispatcherServletinit方法,并且這個方法只會被執(zhí)行一次。通過init方法就會去執(zhí)行到initWebApplicationContext這個方法了,這就是web子容器的一個啟動執(zhí)行順序。


          ????dispatcher
          ????class>org.springframework.web.servlet.DispatcherServletservlet-class>
          ????//?如果不配置這個load-on-startup?1?不會再項目啟動的時候執(zhí)行inti方法。而是首次訪問再啟動
          ????<load-on-startup>1load-on-startup>
          ??servlet>

          大概流程如下:從上述代碼我們可以發(fā)現(xiàn)子容器是自己重新通過反射new了一個新的容器作為子容器, 并且設(shè)置自己的父容器為Spring 初始化創(chuàng)建的WebApplicationContext。然后就是去加載我們在web.xml 里面配置的Springmvc 的配置文件,然后通過創(chuàng)建的子容器去執(zhí)行refresh方法,這個方法我相信很多人應(yīng)該都比較清楚了。

          問題解答

          我們知道了Sping父容器以及SpingMvc子容器的一個啟動過程,以及每個容器都分別干了什么事情現(xiàn)在再回過頭來看看上述四個問題。

          • 為什么需要父子容器?父子容器的主要作用應(yīng)該是劃分框架邊界。有點單一職責的味道。在J2EE三層架構(gòu)中,在service層我們一般使用spring框架來管理, 而在web層則有多種選擇,如spring mvc、struts等。因此,通常對于web層我們會使用單獨的配置文件。例如在上面的案例中,一開始我們使用spring-servlet.xml來配置web層,使用applicationContext.xml來配置servicedao層。如果現(xiàn)在我們想把web層從spring mvc替換成struts,那么只需要將spring-servlet.xml替換成Struts的配置文件struts.xml即可,而applicationContext.xml不需要改變。
          • 是否可以把所有類都通過Spring父容器來管理?(Spring的applicationContext.xml中配置全局掃描)所有的類都通過父容器來管理的配置就是如下:
          "false"??base-package="cn.javajr">
          ????????type="annotation"?expression="org.springframework.stereotype.Service"?/>
          ????????type="annotation"?expression="org.springframework.stereotype.Component"?/>
          ????????type="annotation"?expression="org.springframework.stereotype.Repository"?/>
          ????????type="annotation"?expression="org.springframework.stereotype.Controller"?/>
          ????

          然后在SpringMvc的配置里面不配置掃描包路徑。很顯然這種方式是行不通的,這樣會導致我們請求接口的時候產(chǎn)生404。因為在解析@ReqestMapping注解的過程中initHandlerMethods()函數(shù)只是對Spring MVC 容器中的bean進行處理的,并沒有去查找父容器的bean, 因此不會對父容器中含有@RequestMapping注解的函數(shù)進行處理,更不會生成相應(yīng)的handler。所以當請求過來時找不到處理的handler,導致404。

          • 是否可以把我們所需的類都放入Spring-mvc子容器里面來管理(springmvc的spring-servlet.xml中配置全局掃描)?這個是把包的掃描配置spring-servlet.xml中這個是可行的。為什么可行因為無非就是把所有的東西全部交給子容器來管理了,子容器執(zhí)行了refresh方法,把在它的配置文件里面的東西全部加載管理起來來了。雖然可以這么做不過一般應(yīng)該是不推薦這么去做的,一般人也不會這么干的。如果你的項目里有用到事物、或者aop記得也需要把這部分配置需要放到Spring-mvc子容器的配置文件來,不然一部分內(nèi)容在子容器和一部分內(nèi)容在父容器,可能就會導致你的事物或者AOP不生效。(這里不就有個經(jīng)典的八股文嗎?你有遇到事物不起作用的時候,其實這也是一種情況)
          • 同時通過兩個容器同時來管理所有的類?這個問題應(yīng)該是比較好回答了,肯定不會通過這種方式來的,先不說會不會引發(fā)其他問題,首先兩個容器里面都放一份一樣的對象,造成了內(nèi)存浪費。再者的話子容器會覆蓋父容器加載,本來可能父容器配置了事物生成的是代理對象,但是被子容器一覆蓋,又成了原生對象。這就導致了你的事物不起作用了。在補充一個問題:SpringBoot 里面是否還有父子容器?我們下篇再見!

          總結(jié)

          • 其實父子容器對于程序員來說是無感的,是一個并沒有什么用的知識點,都是Spring幫我們處理了,但是我們還是需要知道有這么個東西,不然我們有可能遇到問題的時候可能不知道如何下手。比如為啥我這個事物不起作用了,我這個aop怎么也不行了,網(wǎng)上都是這么配置的。

          結(jié)束

          • 由于自己才疏學淺,難免會有紕漏,假如你發(fā)現(xiàn)了錯誤的地方,還望留言給我指出來,我會對其加以修正。
          • 如果你覺得文章還不錯,你的轉(zhuǎn)發(fā)、分享、贊賞、點贊、留言就是對我最大的鼓勵。
          • 感謝您的閱讀,十分歡迎并感謝您的關(guān)注。


          • 站在巨人的肩膀上摘蘋果: https://www.cnblogs.com/grasp/p/11042580.html
            https://javajr.cn

          往期精選

          推薦???:Java高并發(fā)編程基礎(chǔ)三大利器之CyclicBarrier

          推薦???:Java高并發(fā)編程基礎(chǔ)三大利器之CountDownLatch

          推薦???:Java高并發(fā)編程基礎(chǔ)三大利器之Semaphore

          推薦???:Java高并發(fā)編程基礎(chǔ)之AQS

          推薦???:可惡的爬蟲直接把生產(chǎn)6臺機器爬掛了!


          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)、等等。獲取方式:點“在看”,關(guān)注公眾號并回復 666?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 67
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  性爱无码片AV | A片黄色电影一级片 | 美女尻屄| 日本的一级黄色片 | 日韩午夜精品一区二区三区 |