SpringMyBatis解析4-MapperScannerConfigurer


如果有成百上千個dao接口呢,那我們豈不是要配置添加成百上千個bean,當然不是這樣,spring還為MyBatis添加了拓展的功能,可以通過掃描包目錄的方式,添加dao,讓我看看具體使用和實現。

<!-- 去掉該配置  
<bean id="personDao" class="org.mybatis.spring.mapper.MapperFactoryBean">  
    <property name="mapperInterface" value="net.itaem.dao.PersonDao"/>  
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>  
</bean>  
 -->  
 <!-- 如果 net.itaem.dao 包下面有很多dao需要注冊,那么可以使用這種掃描的方式添加dao-->  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
    <property name="basePackage" value="net.itaem.dao"/>  
</bean>  

我們屏蔽掉了最原始的代碼(userMapper 的創建)而增加了MapperScannerConfigurer的配置,basePackage屬性是讓你為映射器接口文件設置基本的包路徑。你可以使用分號或逗號作為分隔符設置多於一個的包路徑。每個映射器將會在指定的包路徑中遞歸地被搜索到。被發現的映射器將會使用Spring對自動偵測組件默認的命名策略來命名。也就是說,如果沒有發現注解,它就會使用映射器的非大寫的非完全限定類名。但是如果發現了@Component或JSR-330@Named注解,它會獲取名稱。

public void afterPropertiesSet() throws Exception {
  notNull(this.basePackage, "Property 'basePackage' is required");
}

afterPropertiesSet()方法除了一句對basePackage屬性的驗證代碼外並沒有太多的邏輯實現。

MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor接口,如果MapperScannerConfigurer實現了該接口,那么說明在application初始化的時候該接口會被調用,具體實現,讓我先看看:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {  
  if (this.processPropertyPlaceHolders) {  
    processPropertyPlaceHolders();  
  }  
  
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);  
  scanner.setAddToConfig(this.addToConfig);  
  scanner.setAnnotationClass(this.annotationClass);  
  scanner.setMarkerInterface(this.markerInterface);  
  scanner.setSqlSessionFactory(this.sqlSessionFactory);  
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);  
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);  
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);  
  scanner.setResourceLoader(this.applicationContext);  
  scanner.setBeanNameGenerator(this.nameGenerator);  
  scanner.registerFilters();  
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));  
}  
這里我們重點關注三個主要的方法,分別是
processPropertyPlaceHolders(); 
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

processPropertyPlaceHolders屬性的處理

執行屬性的處理,簡單的說,就是把xml中${XXX}中的XXX替換成屬性文件中的相應的值

  private void processPropertyPlaceHolders() {
    Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);

    if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {
      BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext)
          .getBeanFactory().getBeanDefinition(beanName);

      // PropertyResourceConfigurer does not expose any methods to explicitly perform
      // property placeholder substitution. Instead, create a BeanFactory that just
      // contains this mapper scanner and post process the factory.
      DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
      factory.registerBeanDefinition(beanName, mapperScannerBean);

      for (PropertyResourceConfigurer prc : prcs.values()) { prc.postProcessBeanFactory(factory); }

      PropertyValues values = mapperScannerBean.getPropertyValues();

      this.basePackage = updatePropertyValue("basePackage", values);
      this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
      this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
    }
  }

BeanDefinitionRegistries會在應用啟動的時候調用,並且會早於BeanFactoryPostProcessors的調用,這就意味着PropertyResourceConfigurers還沒有被加載所有對於屬性文件的引用將會失效。為避免此種情況發生,此方法手動地找出定義的PropertyResourceConfigurers並進行提前調用以保證對於屬性的引用可以正常工作。

 

根據配置屬性生成過濾器

scanner.registerFilters();方法會根據配置的屬性生成對應的過濾器,然后這些過濾器在掃描的時候會起作用。

  public void registerFilters() {
    boolean acceptAllInterfaces = true;
    //對於annotationClass屬性的處理
    if (this.annotationClass != null) {
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }
    //對於markerInterface屬性的處理
    if (this.markerInterface != null) {
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }
    if (acceptAllInterfaces) {
      // default include filter that accepts all classes
      addIncludeFilter(new TypeFilter() {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
          return true;
        }
      });
    }
    //不掃描package-info.java文件
    addExcludeFilter(new TypeFilter() {
      @Override
      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
      }
    });
  }

代碼中得知,根據之前屬性的配置生成了對應的過濾器。

(1)annotationClass屬性處理。

如果annotationClass不為空,表示用戶設置了此屬性,那么就要根據此屬性生成過濾器以保證達到用戶想要的效果,而封裝此屬性的過濾器就是AnnotationTypeFilter。AnnotationTypeFilter保證在掃描對應Java文件時只接受標記有注解為annotationClass的接口。

(2)markerInterface屬性處理。

如果markerInterface不為空,表示用戶設置了此屬性,那么就要根據此屬性生成過濾器以保證達到用戶想要的效果,而封裝此屬性的過濾器就是實現AssignableTypeFilter接口的局部類。表示掃描過程中只有實現markerInterface接口的接口才會被接受。

(3)全局默認處理。

在上面兩個屬性中如果存在其中任何屬性,acceptAllInterfaces的值將會改變,但是如果用戶沒有設定以上兩個屬性,那么,Spring會為我們增加一個默認的過濾器實現TypeFilter接口的局部類,旨在接受所有接口文件。

(4)package-info.java處理。

對於命名為package-info的Java文件,默認不作為邏輯實現接口,將其排除掉,使用TypeFilter接口的局部類實現match方法。

從上面的函數我們看出,控制掃描文件Spring通過不同的過濾器完成,這些定義的過濾器記錄在了includeFilters和excludeFilters屬性中。

掃描java文件

scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

  public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    doScan(basePackages);
    //如果配置了includeAnnotationConfig,則注冊對應注解的處理器以保證注解功能的正常使用。
    if (this.includeAnnotationConfig) {
      AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
  }
  //scan是個全局方法,掃描工作通過doScan(basePackages)委托給了doScan方法,同時,還包括了includeAnnotationConfig屬性的處理,
  //AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)代碼主要是完成對於注解處理器的簡單注冊,
  //比如AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor等,這里不再贅述,我們重點研究文件掃描功能的實現。
  protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //如果沒有掃描到任何文件發出警告
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    //掃描basePackage路徑下java文件
    for (String basePackage : basePackages) {
      //如果當前bean是用於生成代理的bean那么需要進一步處理
      //findCandidateComponents方法根據傳入的包路徑信息並結合類文件路徑拼接成文件的絕對路徑,
      //同時完成了文件的掃描過程並且根據對應的文件生成了對應的bean,
    //使用ScannedGenericBeanDefinition類型的bean承載信息,bean中只記錄了resource和source信息。
//我們更感興趣的是isCandidateComponent(metadataReader),此句代碼用於判斷當前掃描的文件是否符合要求,
    //而我們之前注冊的一些過濾器信息也正是在此時派上用場的。
Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { //解析scope屬性 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } //如果是AnnotatedBeanDefinition類型的bean,需要檢測下常用注解如:Primary、Lazy等 if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } //檢測當前bean是否已經注冊 if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
該方法主要做了以下操作:
1)掃描basePackage下面的java文件
2)解析掃描到的java文件
3)調用各個在上一步驟注冊的過濾器,執行相應的方法。
4)為解析后的java注冊bean,注冊方式采用編碼的動態注冊實現。
5)構造MapperFactoryBean的屬性,mapperInterface,sqlSessionFactory等等,填充到BeanDefinition里面去。
做完這些,MapperFactoryBean對象也就構造完成了,掃描方式添加dao的工作也完成了。

總結

其實了解了Spring整合MyBatis的流程,我們也就大體知道Spring整合一些框架所使用的擴展方法,不過大多是都是通過繼承接口的方式,然后通過spring回調該接口的方式,實現我們自己想要的擴展邏輯,所以了解spring提供的一些擴展的接口以及抽象類是擴展的關鍵,就像InitializingBean,BeanDefinitionRegistryPostProcessor這些接口,知道了這些接口調用的方式,以及上面時候會調用,我們就可以知道,我們需要擴展的功能應該實現哪個接口,或者集成哪個抽象類。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM