mybatis-spring 源碼分析MapperScannerConfigurer

在Spring配置Mybatis的文件中我們可以看到如下代碼:
[html] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <!-- 掃描dao -->  
  2. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
  3. <property name="basePackage" value="org.tarena.note.dao">  
  4. </property>  

MapperScannerConfigurer,讓它掃描特定的包,自動幫我們成批地創建映射器。這樣就大大減少了配置的工作量。

basePackage屬性是讓你爲映射器接口文件設置基本的包路徑。可以使用分號或逗號作爲分隔符設置多於一個的包路徑。每個映射器都會在指定的包路徑中遞歸地被搜索到。被發現的映射器將會使用spring對自動偵測組件默認的命名策略來命名。也就是說,如果沒有發現註解,它就會使用映射器的非大寫的非完全限定類名。如果發現了@Component或JSR-330@Named註解,它會獲取名稱。

通過上面的配置,Spring就會幫助我們對test.mybatis.dao下面所有接口進行自動的注入,而不需要爲每個接口重複在Spring配置文件中進行聲明瞭。那麼這個功能如何做到的呢?MapperScanner Configurer中又有哪些核心操作呢?同樣首先看下這個類實現了InitializingBean接口。馬上查找afterPropertiesSet方法來看看類的初始化邏輯。

[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {  
但沒有任何有意義的實現,我們看到MapperScannerConfigurer還實現了接口BeanDefinitionRegistryPostProcessor接口的方法:
[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {  
  2.   if (this.processPropertyPlaceHolders) {  
  3.     processPropertyPlaceHolders();  
  4.   }  
  5.   
  6.   ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);  
  7.   scanner.setAddToConfig(this.addToConfig);  
  8.   scanner.setAnnotationClass(this.annotationClass);  
  9.   scanner.setMarkerInterface(this.markerInterface);  
  10.   scanner.setSqlSessionFactory(this.sqlSessionFactory);  
  11.   scanner.setSqlSessionTemplate(this.sqlSessionTemplate);  
  12.   scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);  
  13.   scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);  
  14.   scanner.setResourceLoader(this.applicationContext);  
  15.   scanner.setBeanNameGenerator(this.nameGenerator);  
  16.   scanner.registerFilters();  
  17.   scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));  
  18. }  

正是這裏。大致看下代碼實現,正是完成了對指定路徑掃描的邏輯。那麼,我們就以此爲入口,詳細地分析MapperScannerConfigurer所提供的邏輯實現

1.processPropertyPlaceHolders屬性的處理

首先,難題就是processPropertyPlaceHolders屬性的處理。或許許多人並未接觸過此屬性。我們只能查看processPropertyPlaceHolders()函數來反推此屬性所代表的功能。

[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. private void processPropertyPlaceHolders() {  
  2.   Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);  
  3.   
  4.   if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {  
  5.     BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext)  
  6.         .getBeanFactory().getBeanDefinition(beanName);  
  7.   
  8.     // PropertyResourceConfigurer does not expose any methods to explicitly perform  
  9.     // property placeholder substitution. Instead, create a BeanFactory that just  
  10.     // contains this mapper scanner and post process the factory.  
  11.     DefaultListableBeanFactory factory = new DefaultListableBeanFactory();  
  12.     factory.registerBeanDefinition(beanName, mapperScannerBean);  
  13.   
  14.     for (PropertyResourceConfigurer prc : prcs.values()) {  
  15.       prc.postProcessBeanFactory(factory);  
  16.     }  
  17.   
  18.     PropertyValues values = mapperScannerBean.getPropertyValues();  
  19.   
  20.     this.basePackage = updatePropertyValue("basePackage", values);  
  21.     this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);  
  22.     this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);  
  23.   }  
  24. }  

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

下面我們舉個例子說明:

如果在配置文件中添加如下代碼 

[html] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <pre name="code" class="java"><bean class="org.mybatis.Spring.mapper.MapperScannerConfigurer">  
  2.       <property name="basePackage" value="${basePackage}"/>  
  3. </bean>  



此時你會發現這個配置並沒有生效。因爲在解析MapperScannerConfigurer這個bean的時候,配置文件還沒有被加載,爲了解決這個問題,Spring提供了processPropertyPlaceHloder屬性,你需要這樣配置MapperScannerConfigurer類型的bean:

[html] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <pre name="code" class="java"><bean class="org.mybatis.Spring.mapper.MapperScannerConfigurer">  
  2.       <property name="basePackage" value="${basePackage}"/>  
  3.       <property name="processPropertyPlaceHolders" value="true">  
  4. </bean>  



通過processPropertyPlaceHolders屬性的配置,將程序引入我們正在分析的processPropertyPlaceHolders函數中來完成屬性文件的加載。至此,我們理清了這個函數的屬性,再回顧看下這個函數做的事情:

(1)找到所有已經註冊的PropertyResourceConfigurer類型的bean。

(2)模擬Spring的環境來用處理器。這裏通過使用呢new DefaultlistableBeanFactory()來模擬Spring中的環境(完成處理器的調用後便失效),將映射的bean,也就是MapperScannerConfigurer類型bean註冊到環境中來進行後處理器的調用。

2.根據配置屬性生成過濾器  

在postProcessBeanDefinitionRegistry方法中可以看到,配置中支持很多屬性的設定,但是我們感興趣的或者說影響掃描結果的並不多,屬性設置後通過在scanner.registerFilters()代碼中生成對應的過濾器來控制掃描結果。

[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public void registerFilters() {  
  2.   boolean acceptAllInterfaces = true;  
  3.   
  4.   // if specified, use the given annotation and / or marker interface  
  5.   //對於annotationClass屬性的處理  
  6.   /* 
  7.    *如果annotationClass不爲空,表示用戶設置了此屬性,那麼就要根據此屬性生成過濾器以保證達到用戶 
  8.    *想要的效果,而封裝此屬性的過濾器就是AnnotationTypeFilter.AnnotationTypeFilter保證在掃描對應 
  9.    *java文件時只接受標記有註解爲annotationClass接口  
  10.    */  
  11.   if (this.annotationClass != null) {  
  12.     addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));  
  13.     acceptAllInterfaces = false;  
  14.   }  
  15.   
  16.   // override AssignableTypeFilter to ignore matches on the actual marker interface  
  17.   //對於markerInterface屬性的處理  
  18.   if (this.markerInterface != null) {  
  19.     addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {  
  20.       @Override  
  21.       protected boolean matchClassName(String className) {  
  22.         return false;  
  23.       }  
  24.     });  
  25.     acceptAllInterfaces = false;  
  26.   }  
  27.   //全局默認處理  
  28.   /* 
  29.    * 在上面兩個屬性中如果存在其中任何屬性,acceptAllInterfaces的值將會被改變,但是如果用戶沒有設定以上的屬性 
  30.    * 那麼,Spring會爲我們增加一個默認的過濾器實現TypeFilter接口的局部類,旨在接受所有接口文件。 
  31.    */  
  32.   if (acceptAllInterfaces) {  
  33.     // default include filter that accepts all classes  
  34.     addIncludeFilter(new TypeFilter() {  
  35.       public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {  
  36.         return true;  
  37.       }  
  38.     });  
  39.   }  
  40.   
  41.   // exclude package-info.java  
  42.   //不掃描package-info.java文件  
  43.   addExcludeFilter(new TypeFilter() {  
  44.     public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {  
  45.       String className = metadataReader.getClassMetadata().getClassName();  
  46.       return className.endsWith("package-info");  
  47.     }  
  48.   });  
  49. }  

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

[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public void addIncludeFilter(TypeFilter includeFilter){  
  2.        this.includeFilters.add(includeFilter);  
  3. }  
  4.   
  5. public void addExcludeFilter(TypeFilter excludeFilter){  
  6.        this.excludeFilters.add(0,excludeFilter);  
  7. }  

至於過濾器爲什麼會在掃描過程中起作用,我們在後面分析掃描實現的時候再深入研究。

3.掃描Java文件

設置了相關屬性以及生成了對應的過濾器後就可以進行文件的掃描了,掃描工作是有ClassPathMapperScanner類的父類ClassPathBeanDefinitionScanner的scan方法完成的。

[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public int scan(String... basePackages) {  
  2.     int beanCountAtScanStart = this.registry.getBeanDefinitionCount();  
  3.   
  4.     doScan(basePackages);  
  5.   
  6.     // Register annotation config processors, if necessary.  
  7.     if (this.includeAnnotationConfig) {  
  8.         AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);  
  9.     }  
  10.   
  11.     return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);  
  12. }  

scan是個全局方法,掃描工作通過
[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. doScan(basePackages);  
委託給了doScan方法,同時,還包括了includeAnnotationConfig屬性的處理,AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);代碼主要是完成對於註解處理器的簡單註冊,我們下面主要分析下掃描功能的實現  

[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public Set<BeanDefinitionHolder> doScan(String... basePackages) {  
  2.   Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);  
  3.   
  4.   if (beanDefinitions.isEmpty()) {  
  5.     //沒有掃描到任何文件發出警告  
  6.     logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");  
  7.   } else {  
  8.     for (BeanDefinitionHolder holder : beanDefinitions) {  
  9.       GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();  
  10.   
  11.       if (logger.isDebugEnabled()) {  
  12.         logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()   
  13.             + "' and '" + definition.getBeanClassName() + "' mapperInterface");  
  14.       }  
  15.   
  16.       // the mapper interface is the original class of the bean  
  17.       // but, the actual class of the bean is MapperFactoryBean  
  18.       //開始構造MapperFactoryBean類型的bean.  
  19.       definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());  
  20.       definition.setBeanClass(MapperFactoryBean.class);  
  21.   
  22.       definition.getPropertyValues().add("addToConfig"this.addToConfig);  
  23.   
  24.       boolean explicitFactoryUsed = false;  
  25.       if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {  
  26.         definition.getPropertyValues().add("sqlSessionFactory"new RuntimeBeanReference(this.sqlSessionFactoryBeanName));  
  27.         explicitFactoryUsed = true;  
  28.       } else if (this.sqlSessionFactory != null) {  
  29.         definition.getPropertyValues().add("sqlSessionFactory"this.sqlSessionFactory);  
  30.         explicitFactoryUsed = true;  
  31.       }  
  32.   
  33.       if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {  
  34.         if (explicitFactoryUsed) {  
  35.           logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");  
  36.         }  
  37.         definition.getPropertyValues().add("sqlSessionTemplate"new RuntimeBeanReference(this.sqlSessionTemplateBeanName));  
  38.         explicitFactoryUsed = true;  
  39.       } else if (this.sqlSessionTemplate != null) {  
  40.         if (explicitFactoryUsed) {  
  41.           logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");  
  42.         }  
  43.         definition.getPropertyValues().add("sqlSessionTemplate"this.sqlSessionTemplate);  
  44.         explicitFactoryUsed = true;  
  45.       }  
  46.   
  47.       if (!explicitFactoryUsed) {  
  48.         if (logger.isDebugEnabled()) {  
  49.           logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");  
  50.         }  
  51.         definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);  
  52.       }  
  53.     }  
  54.   }  
  55.   
  56.   return beanDefinitions;  
  57. }  

此時,雖然還沒有完成介紹到掃描的過程,但是我們也應該理解了Spring中對於自動掃描的註冊,聲明MapperScannerConfigurer類型的bean目的是不需要我們對於每個接口都註冊一個MapperFactoryBean類型的對應的bean,但是,不再配置文件中註冊並不代表這個bean不存在,而是在掃描的過程中通過編碼的方式動態註冊。實現過程我們在上面的函數中可以看得非常清楚  
[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {  
  2.     Assert.notEmpty(basePackages, "At least one base package must be specified");  
  3.     Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();  
  4.     for (String basePackage : basePackages) {  
  5.         //掃描basePackage路徑下的java文件  
  6.         Set<BeanDefinition> candidates = findCandidateComponents(basePackage);  
  7.         for (BeanDefinition candidate : candidates) {  
  8.             //解析scope屬性  
  9.             ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);  
  10.             candidate.setScope(scopeMetadata.getScopeName());  
  11.             String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);  
  12.             if (candidate instanceof AbstractBeanDefinition) {  
  13.                 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);  
  14.             }  
  15.             if (candidate instanceof AnnotatedBeanDefinition) {  
  16.                 //如果是AnnotationBeanDefinition類型的bean需要檢測下常用註解如:Primary,Lazy等。  
  17.                 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);  
  18.             }  
  19.             //檢測當前bean是否已經註冊  
  20.             if (checkCandidate(beanName, candidate)) {  
  21.                 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);  
  22.                 //如果當前bean是用於生成代理的bean那麼需要進一步處理  
  23.                 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);  
  24.                 beanDefinitions.add(definitionHolder);  
  25.                 registerBeanDefinition(definitionHolder, this.registry);  
  26.             }  
  27.         }  
  28.     }  
  29.     return beanDefinitions;  
  30. }  

[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public Set<BeanDefinition> findCandidateComponents(String basePackage) {  
  2.     Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();  
  3.     try {  
  4.         String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +  
  5.                 resolveBasePackage(basePackage) + "/" + this.resourcePattern;  
  6.         Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);  
  7.         boolean traceEnabled = logger.isTraceEnabled();  
  8.         boolean debugEnabled = logger.isDebugEnabled();  
  9.         for (Resource resource : resources) {  
  10.             if (traceEnabled) {  
  11.                 logger.trace("Scanning " + resource);  
  12.             }  
  13.             if (resource.isReadable()) {  
  14.                 try {  
  15.                     MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);  
  16.                     if (isCandidateComponent(metadataReader)) {  
  17.                         ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);  
  18.                         sbd.setResource(resource);  
  19.                         sbd.setSource(resource);  
  20.                         if (isCandidateComponent(sbd)) {  
  21.                             if (debugEnabled) {  
  22.                                 logger.debug("Identified candidate component class: " + resource);  
  23.                             }  
  24.                             candidates.add(sbd);  
  25.                         }  
  26.                         else {  
  27.                             if (debugEnabled) {  
  28.                                 logger.debug("Ignored because not a concrete top-level class: " + resource);  
  29.                             }  
  30.                         }  
  31.                     }  
  32.                     else {  
  33.                         if (traceEnabled) {  
  34.                             logger.trace("Ignored because not matching any filter: " + resource);  
  35.                         }  
  36.                     }  
  37.                 }  
  38.                 catch (Throwable ex) {  
  39.                     throw new BeanDefinitionStoreException(  
  40.                             "Failed to read candidate component class: " + resource, ex);  
  41.                 }  
  42.             }  
  43.             else {  
  44.                 if (traceEnabled) {  
  45.                     logger.trace("Ignored because not readable: " + resource);  
  46.                 }  
  47.             }  
  48.         }  
  49.     }  
  50.     catch (IOException ex) {  
  51.         throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);  
  52.     }  
  53.     return candidates;  
  54. }  

findCandidateComponents方法根據傳入的包路徑信息並結合類文件路徑拼接成文件的絕對路徑,同時完成了文件的掃描過程並且根據對應的文件生成了對應的bean,使用ScannedGenericBeanDefinition類型的bean承載信息,bean中值記錄了resource和source信息。這裏,我們更感興趣的是isCandidateCompanent(metadataReader),此句代碼用於判斷當前掃描的文件是否符合要求,而我們之前註冊的過濾器也是在此派上用場的。
[java] view plain copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {  
  2.     for (TypeFilter tf : this.excludeFilters) {  
  3.         if (tf.match(metadataReader, this.metadataReaderFactory)) {  
  4.             return false;  
  5.         }  
  6.     }  
  7.     for (TypeFilter tf : this.includeFilters) {  
  8.         if (tf.match(metadataReader, this.metadataReaderFactory)) {  
  9.             AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();  
  10.             if (!metadata.isAnnotated(Profile.class.getName())) {  
  11.                 return true;  
  12.             }  
  13.             AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);  
  14.             return this.environment.acceptsProfiles(profile.getStringArray("value"));  
  15.         }  
  16.     }  
  17.     return false;  
  18. }  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章