Spring 和SpringMVC 的父子容器關(guān)系

Spring和SpringMVC作為Bean管理容器和MVC層的默認框架,已被眾多WEB應用采用,而實際使用時,由于有了強大的注解功能,很多基于XML的配置方式已經(jīng)被替代,但是在實際項目中,同時配置Spring和SpringMVC時會出現(xiàn)一些奇怪的異常,比如Bean被多次加載,多次實例化,或者依賴注入時,Bean不能被自動注入,但是明明你已經(jīng)將該Bean注冊了的。找原因還是要看問題的根源,我們從容器說起。

在Spring整體框架的核心概念中,容器是核心思想,就是用來管理Bean的整個生命周期的,而在一個項目中,容器不一定只有一個,Spring中可以包括多個容器,而且容器有上下層關(guān)系,目前最常見的一種場景就是在一個項目中引入Spring和SpringMVC這兩個框架,其實就是2個容器,Spring是根容器,SpringMVC是其子容器,并且在Spring根容器中對于SpringMVC容器中的Bean是不可見的,而在SpringMVC容器中對于Spring根容器中的Bean是可見的,也就是子容器可以看見父容器中的注冊的Bean,反之就不行。理解這點很重要,因為這是一個規(guī)則,是Spring自己設定的,但是往下看,我們會發(fā)現(xiàn)有些地方它并不默認使用這個規(guī)則。

當我們使用注解時,對于Bean注冊這個功能的實現(xiàn)就不需要在給每個Bean配置XML了,只要使用統(tǒng)一的如下配置即可。
<context:component-scan base-package=“com.test" />
根據(jù)Spring提供的參考手冊,該配置的功能是掃描默認包下的所有的@Component注解,并且自動注冊到容器中,同時也掃描@Controller,@Service,@Respository這三個注解,他們是繼承自@Component。

除了以上我們使用的掃描配置,在項目中我們經(jīng)常見到的就是<context:annotation-config/>這個配置,其實有了以上的配置,這個是可以省略掉的。

還有一個SpringMVC相關(guān)的是<mvc:annotation-driven />配置,經(jīng)過驗證,這個是必須要配置的,因為它是和@RequestMapping結(jié)合使用的,這里補充下SpringMVC框架相關(guān)的知識點。
HandlerMapping,是SpringMVC中用來處理Request請求URL到具體Controller的,其自身也分成很多種類; HandlerAdapter,是SpringMVC中用來處理具體請求映射到具體方法的,其自身也分很多種類;
@RequestMapping這個注解的主要目的就是對具體的Controller和方法進行注冊,以方便HandlerMapping用來處理請求的映射。但是@RequestMapping需要結(jié)合<mvc:annotation-driven />使用才能生效。

好了,有了以上基礎知識的鋪墊,我們看下現(xiàn)在這樣的一個使用場景中,Spring與SpringMVC的容器沖突的原因在那里!

Spring配置文件applicationContext.xml,SpringMVC配置文件applicationContext-MVC.xml,這樣項目中就有2個容器了,配置方式A,如下:
applicationContext.xml中配置了<context:component-scan base-package=“com.test" />,負責所有需要注冊的Bean的掃描工作,applicationContext-MVC.xml中配置<mvc:annotation-driven />,負責springMVC相關(guān)注解的使用,啟動項目發(fā)現(xiàn),springMVC失效,無法進行跳轉(zhuǎn),開啟log的DEBUG級別進行調(diào)試,發(fā)現(xiàn)springMVC容器中的請求好像沒有映射到具體controller中;

配置方式B,如下:
為了快速驗證效果,將<context:component-scan base-package=“com.test" />掃描配置到applicationContext-MVC.xml中,重啟后,驗證成功,springMVC跳轉(zhuǎn)有效。

要想查看具體原因,翻看源碼,從springMVC的DispatcherServlet開始看,在一個請求進來之后,發(fā)生了什么?漫長的查看之后,找到原因,如下。

springMVC初始化時,會尋找所有當前容器中的所有@Controller注解的Bean,來確定其是否是一個handler,而當前容器springMVC中注冊的Bean中并沒有@Controller注解的,注意,上面提及的配置方式A,所有的@Controller配置的Bean都注冊在Spring這個父容器中了,看代碼。

protected void initHandlerMethods() {

           if (logger.isDebugEnabled()) {

               logger.debug( "Looking for request mappings in application context: " + getApplicationContext());

          }

          String[] beanNames = (this .detectHandlerMethodsInAncestorContexts ?

                    BeanFactoryUtils.beanNamesForTypeIncludingAncestors( getApplicationContext(), Object. class) :

                    getApplicationContext().getBeanNamesForType(Object. class ));

           for (String beanName : beanNames ) {

               if (isHandler(getApplicationContext().getType( beanName))) {
                    detectHandlerMethods( beanName);

              }

          }

          handlerMethodsInitialized( getHandlerMethods());

     }

在方法isHandler中會判斷當前bean的注解是否是controller,代碼如下:

protected boolean isHandler(Class<?> beanType) {

        return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;

}

在配置方式B中,springMVC容器中包括了所有的@Controller注解的Bean,所以自然就能找到了。

以上是原因,解決辦法是什么?注意看initHandlerMethods()方法中,detectHandlerMethodsInAncestorContexts這個Switch,它主要控制從那里獲取容器中的bean,是否包括父容器,默認是不包括的。所以解決辦法是有的,即在springMVC的配置文件中配置HandlerMapping的detectHandlerMethodsInAncestorContexts屬性為true即可(這里需要根據(jù)具體項目看使用的是哪種HandlerMapping),讓其檢測父容器的bean。如下:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">

        <property name="detectHandlerMethodsInAncestorContexts">

            <value>true</value>

        </property>

</bean>

以上已經(jīng)有了2種解決方案了,但在實際工程中,會包括很多配置,根據(jù)不同的業(yè)務模塊來劃分,所以我們一般思路是各負其責,明確邊界,Spring根容器負責所有其他非controller的Bean的注冊,而SpringMVC只負責controller相關(guān)的Bean的注冊。第三種方案如下:

Spring容器配置,排除所有@controller的Bean

<context:component-scan base-package="com.fsnip.open">

        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>

</context:component-scan>

SpringMVC容器配置,讓其只包括@controller的Bean

<context:component-scan base-package="com.fsnip.open" use-default-filters="false">

        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />

</context:component-scan>

個人比較推薦第三種方案。引申一下,項目中使用事務的配置方案,也會在這種場景下失效,歸根結(jié)底也是由于2個容器的可見性問題導致,可以結(jié)合具體問題按照上面的思路進行查找原因!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,692評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,285評論 6 342
  • 什么是Spring Spring是一個開源的Java EE開發(fā)框架。Spring框架的核心功能可以應用在任何Jav...
    jemmm閱讀 16,785評論 1 133
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風情閱讀 1,861評論 0 3
  • 第二話 江湖再見 “偉霆,我不想拍?!背聊嗽S久,趙麗穎發(fā)聲了。既然迂回無用,那么,就直來直往吧,她也累了,不想再...
    等猴抱兔閱讀 772評論 1 21

友情鏈接更多精彩內(nèi)容