結(jié)合源碼淺談Spring容器與其子容器Spring MVC 沖突問題

容器是整個Spring 框架的核心思想缓屠,用來管理Bean的整個生命周期。

一個項(xiàng)目中引入Spring和SpringMVC這兩個框架护侮,Spring是父容器敌完,SpringMVC是其子容器,子容器可以看見父容器中的注冊的Bean羊初,反之就不行滨溉。請記住這個特性。

spring 容器基礎(chǔ)釋義

1

我們可以使用統(tǒng)一的如下注解配置來對Bean進(jìn)行批量注冊长赞,而不需要再給每個Bean單獨(dú)使用xml的方式進(jìn)行配置晦攒。

<context:component-scan base-package="com.amu.modules" />

該配置的功能是掃描配置的base-package包下的所有使用了@Component注解的類,并且將它們自動注冊到容器中得哆,同時也掃描其子類 @Controller勤家,@Service,@Respository這三個注解

2

<context:annotation-config/>

此配置表示默認(rèn)聲明了@Required柳恐、@Autowired伐脖、 @PostConstruct、@PersistenceContext乐设、@Resource讼庇、@PreDestroy等注解。

3

<mvc:annotation-driven />

SpringMVC必備配置近尚。它聲明了@RequestMapping蠕啄、@RequestBody、@ResponseBody等戈锻。并且歼跟,該配置默認(rèn)加載很多的參數(shù)綁定方法,比如json轉(zhuǎn)換解析器等格遭。

4 上面的配置等價于spring3.1之后的版本:

<!--配置注解控制器映射器,它是SpringMVC中用來將Request請求URL到映射到具體Controller-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--配置注解控制器映射器,它是SpringMVC中用來將具體請求映射到具體方法-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

二:案例分析

2.1 案例初探

Spring容器與其子容器Spring MVC 沖突問題的原因到底在那里哈街?

我們知道,Spring和SpringMVC 容器配置文件分別為applicationContext.xml和applicationContext-MVC.xml拒迅。

1.在applicationContext.xml中配置了<context:component-scan base-package=“com.amu.modules" />

負(fù)責(zé)所有需要注冊的Bean的掃描和注冊工作骚秦。

2.在applicationContext-MVC.xml中配置<mvc:annotation-driven />

負(fù)責(zé)SpringMVC相關(guān)注解的使用。

3.DEBUG 模式下啟動項(xiàng)目璧微,我們發(fā)現(xiàn)SpringMVC無法進(jìn)行跳轉(zhuǎn)作箍,將log的日志打印發(fā)現(xiàn)SpringMVC容器中的請求沒有映射到具體controller中。
4.在applicationContext-MVC.xml中配置<context:component-scan base-package=“com.amu.modules" />

重啟后前硫,SpringMVC跳轉(zhuǎn)有效胞得。

2.2 查看源碼

看源碼SpringMVC的DispatcherServlet,當(dāng)SpringMVC初始化時屹电,會尋找SpringMVC容器中的所有使用了@Controller注解的Bean阶剑,來確定其是否是一個handler。

第1,2兩步的配置使得當(dāng)前springMVC容器中并沒有注冊帶有@Controller注解的Bean嗤详,而是把所有帶有@Controller注解的Bean都注冊在Spring這個父容器中了个扰,所以springMVC找不到處理器,不能進(jìn)行跳轉(zhuǎn)葱色。(結(jié)合上文知識點(diǎn)理解)

核心源碼如下:

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中會判斷當(dāng)前bean的注解是否是controller:

protected boolean isHandler(Class<?> beanType) {
  return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
}

在第4步配置中递宅,SpringMVC容器中也注冊了所有帶有@Controller注解的Bean,故SpringMVC能找到處理器進(jìn)行處理苍狰,從而正常跳轉(zhuǎn)办龄。

原因找到了,那么如何解決呢淋昭?

2.2 解決辦法

在initHandlerMethods()方法中俐填,detectHandlerMethodsInAncestorContexts這個Switch,它主要控制獲取哪些容器中的bean以及是否包括父容器翔忽,默認(rèn)是不包括的英融。

解決辦法:在springMVC的配置文件中配置HandlerMapping的detectHandlerMethodsInAncestorContexts屬性為true(根據(jù)具體項(xiàng)目看使用的是哪種HandlerMapping)盏檐,讓它檢測父容器的bean。

如下:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
   <property name="detectHandlerMethodsInAncestorContexts">
       <value>true</value>
   </property>
</bean>

我們按照官方推薦根據(jù)不同的業(yè)務(wù)模塊來劃分不同容器中注冊不同類型的Bean:Spring父容器負(fù)責(zé)所有其他非@Controller注解的Bean的注冊驶悟,而SpringMVC只負(fù)責(zé)@Controller注解的Bean的注冊胡野,使得他們各負(fù)其責(zé)、明確邊界痕鳍。

配置方式如下

1.在applicationContext.xml中配置:

<!-- Spring容器中注冊非@controller注解的Bean -->
<context:component-scan base-package="com.amu.modules">
   <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

2.applicationContext-MVC.xml中配置

<!-- SpringMVC容器中只注冊帶有@controller注解的Bean -->
<context:component-scan base-package="com.amu.modules" use-default-filters="false">
   <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

小結(jié):

把不同類型的Bean分配到不同的容器中進(jìn)行管理硫豆。

三:進(jìn)階:use-default-filters="false"的作用

3.1 初探

spring-mvc.xml 的不同配置方法

1 只掃描到帶有@Controller注解的Bean

如下配置會成功掃描到帶有@Controller注解的Bean,不會掃描帶有@Service/@Repository注解的Bean笼呆,是正確的熊响。

<context:component-scan base-package="com.amu.modules.controller">   
     <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>   
</context:component-scan>  
2 掃描到其他注解

但是如下方式,不僅僅掃描到帶有@Controller注解的Bean诗赌,還掃描到帶有@Service/@Repository注解的Bean汗茄,可能造成事務(wù)不起作用等問題。

<context:component-scan base-package="com.amu.modules">   
     <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>   
</context:component-scan>  
這是因?yàn)槭裁茨兀?/h5>

3.2 源碼分析

1.<context:component-scan>會交給org.springframework.context.config.ContextNamespaceHandler處理.

registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); 

2.ComponentScanBeanDefinitionParser會讀取配置文件信息并組裝成org.springframework.context.annotation.ClassPathBeanDefinitionScanner進(jìn)行處理境肾。

3.<context:component-scan>的use-default-filters屬性默認(rèn)為true剔难,在創(chuàng)建ClassPathBeanDefinitionScanner時會根據(jù)use-default-filters是否為true來調(diào)用如下代碼:

protected void registerDefaultFilters() {
  this.includeFilters.add(new AnnotationTypeFilter(Component.class));
  ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
  try {
    this.includeFilters.add(new AnnotationTypeFilter(
      ((Class<? extends Annotation>) cl.loadClass("javax.annotation.ManagedBean")), false));
     logger.info("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
  }
  catch (ClassNotFoundException ex) {
    // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
  }
  try {
    this.includeFilters.add(new AnnotationTypeFilter(
      ((Class<? extends Annotation>) cl.loadClass("javax.inject.Named")), false));
    logger.info("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
  }
  catch (ClassNotFoundException ex) {
    // JSR-330 API not available - simply skip.
  }
}

從源碼我們可以看出默認(rèn)ClassPathBeanDefinitionScanner會自動注冊對@Component、@ManagedBean奥喻、@Named注解的Bean進(jìn)行掃描偶宫。

4.在進(jìn)行掃描時會通過include-filter/exclude-filter來判斷你的Bean類是否是合法的:

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
  for (TypeFilter tf : this.excludeFilters) {
    if (tf.match(metadataReader, this.metadataReaderFactory)) {
       return false;
    }
  }
  for (TypeFilter tf : this.includeFilters) {
    if (tf.match(metadataReader, this.metadataReaderFactory)) {
       AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
       if (!metadata.isAnnotated(Profile.class.getName())) {
          return true;
       }
       AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
       return this.environment.acceptsProfiles(profile.getStringArray("value"));
     }
   }
  return false;
}

從源碼可看出:掃描時首先通過exclude-filter 進(jìn)行黑名單過濾,然后通過include-filter 進(jìn)行白名單過濾环鲤,否則默認(rèn)排除纯趋。

3.3 結(jié)論

在spring-mvc.xml中進(jìn)行如下配置:

<context:component-scan base-package="com.amu.modules"> 
     <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 
</context:component-scan>

SpringMVC容器不僅僅掃描并注冊帶有@Controller注解的Bean,而且還掃描并注冊了帶有@Component的子注解@Service冷离、@Reposity的Bean吵冒,從而造成新加載的bean覆蓋了老的bean,但事務(wù)的AOP代理沒有配置在spring-mvc.xml配置文件中西剥,造成事務(wù)失效痹栖。因?yàn)閡se-default-filters默認(rèn)為true。

結(jié)論:use-default-filters=“false”禁用掉默認(rèn)瞭空。

【公眾號】:一只阿木木

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揪阿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子咆畏,更是在濱河造成了極大的恐慌南捂,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旧找,死亡現(xiàn)場離奇詭異溺健,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)钮蛛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門鞭缭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剖膳,“玉大人,你說我怎么就攤上這事岭辣〕泵兀” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵易结,是天一觀的道長。 經(jīng)常有香客問我柜候,道長搞动,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任渣刷,我火速辦了婚禮鹦肿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辅柴。我一直安慰自己箩溃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布碌嘀。 她就那樣靜靜地躺著涣旨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪股冗。 梳的紋絲不亂的頭發(fā)上霹陡,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機(jī)與錄音止状,去河邊找鬼烹棉。 笑死,一個胖子當(dāng)著我的面吹牛怯疤,可吹牛的內(nèi)容都是我干的浆洗。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼集峦,長吁一口氣:“原來是場噩夢啊……” “哼伏社!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起少梁,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤洛口,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后凯沪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體第焰,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年妨马,在試婚紗的時候發(fā)現(xiàn)自己被綠了挺举。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杀赢。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖湘纵,靈堂內(nèi)的尸體忽然破棺而出脂崔,到底是詐尸還是另有隱情,我是刑警寧澤梧喷,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布砌左,位于F島的核電站,受9級特大地震影響铺敌,放射性物質(zhì)發(fā)生泄漏汇歹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一偿凭、第九天 我趴在偏房一處隱蔽的房頂上張望产弹。 院中可真熱鬧,春花似錦弯囊、人聲如沸痰哨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斤斧。三九已至,卻和暖如春奄毡,著一層夾襖步出監(jiān)牢的瞬間折欠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工吼过, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锐秦,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓盗忱,卻偏偏與公主長得像酱床,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子趟佃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容