context:component-scan標籤的use-default-filters屬性的作用以及原理分析

阿豪聊乾貨發表於2016-09-16

一、背景

  我們在Spring+SpringMVC+Mybatis的整合開發中,經常會遇到事務配置不起作用等問題,那麼本文就來分析下出現這種問題可能的原因以及解決方式。

二、分析及原理窺探

  1.專案結構

  

  2.我們在spring-mvc.xml檔案中進行如下配置,這種方式會成功掃描到帶有@Controller註解的Bean,不會掃描帶有@Service/@Repository註解的Bean,是正確的。

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

  3.但是如下方式,不僅僅掃描到帶有@Controller註解的Bean,還掃描到帶有@Service/@Repository註解的Bean,可能造成事務不起作用等問題。

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

這是因為什麼呢?下面讓我們來從原始碼進行分析。

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

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

  2.ComponentScanBeanDefinitionParser會讀取配置檔案資訊並組裝成org.springframework.context.annotation.ClassPathBeanDefinitionScanner進行處理。

  3.如果沒有配置<context:component-scan>的use-default-filters屬性,則預設為true,在建立ClassPathBeanDefinitionScanner時會根據use-default-filters是否為true來呼叫如下程式碼:

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.
  }
}

從以上原始碼我們可以看出預設ClassPathBeanDefinitionScanner會自動註冊對@Component、@ManagedBean、@Named註解的Bean進行掃描。

  4.在進行掃描時會通過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 進行黑名單過濾,然後通過include-filter 進行白名單過濾,否則預設排除。

三、結論

  在spring-mvc.xml中進行如下配置:

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

則SpringMVC容器不僅僅掃描並註冊帶有@Controller註解的Bean,而且還掃描並註冊了帶有@Component的子註解@Service、@Reposity的Bean。因為use-default-filters預設為true。所以如果不需要預設的,則use-default-filters=“false”禁用掉。

  當我們進行上面的配置時,SpringMVC容器會把service、dao層的bean重新載入,從而造成新載入的bean覆蓋了老的bean,但事務的AOP代理沒有配置在spring-mvc.xml配置檔案中,造成事務失效。解決辦法是:在spring-mvc.xml配置檔案中的context:component-scan標籤中使用use-default-filters=“false”禁用掉預設的行為。

相關文章