@Mapper注解和@MapperScan注解是我們使用mybatis-spring的常用注解,之前為了探究兩個注解的關聯性,百度了一波文章,但是都將@Mapper注解和@MapperScan注解分開講解,索性自己結合mybatis-spring和spring-boot原始碼分析,探究兩個注解的關聯性,
整篇文章以MapperScannerRegistrar、MybatisAutoConfiguration、AutoConfiguredMapperScannerRegistrar三個類作為核心類分析
一、啟動類定義MapperScan注解的方式
1,入口得從@MapperScan注解來說
從@MapperScan注解的內容可以看出,如果SpringBoot主類定義了MapperScan注解,那么MapperScannerRegistrar類會將BeanDefinition注入到SpringBoot的BeanDefinitionRegistry組件中,這里BeanDefinitionRegistry我就不多加贅述,有興趣的朋友可以看看SpringBoot加載BeanDefinitionRegistry與BeanDefinitionRegistry實體化Bean的原始碼,

2,觀察MapperScannerRegistrar類,其實作了ImportBeanDefinitionRegistrar介面,那么在SpringBoot的refresh背景關系的invokeBeanDefinitionRegistryPostProcessors方法的程序中會呼叫MapperScannerRegistrar的registerBeanDefinitions方法,而MapperScannerRegistrar的registerBeanDefinitions方法中最主要的方法是registerBeanDefinitions方法,


這里我將MapperScannerRegistrar的registerBeanDefinitions方法中的相關內容貼出來:
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
從registerBeanDefinitions方法不難看出,這里獲取了MapperScanner注解定義的annotationClass和basePackages和basePackageClasses三個屬性,然后注入了一個MapperScannerConfigurer的BeanDefinition到SpringBoot的BeanDefinitionRegistry組件中,
3,觀察MapperScannerConfigurer類,其實作了BeanDefinitionRegistryPostProcessor介面,那么在SpringBoot的refresh背景關系的invokeBeanDefinitionRegistryPostProcessors方法的程序中會呼叫MapperScannerRegistrar的postProcessBeanDefinitionRegistry方法(這里和MapperScannerRegistrar的registerBeanDefinitions方法呼叫方法相同,但是入口不同,有興趣可以看下PostProcessorRegistrationDelegate類,搜索關鍵字BeanDefinitionRegistryPostProcessor),

這里我將MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法中的相關內容貼出來:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAnnotationClass(this.annotationClass);
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
這里的核心類是ClassPathMapperScanner類(ClassPathMapperScanner類是SpringBoot批量為Interface生產BeanDefinition工具類),
首先這里會呼叫scanner.registerFilters()方法,這里會將MapperScan注解定義的annotationClass屬性設定到ClassPathMapperScanner類中,然后呼叫 scanner.registerFilters(),這個方法的作用是設定一個TypeFilter的方法介面到這個scanner物件的includeFilters屬性中,
然后呼叫scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS))方法,這里最侄訓呼叫ClassPathMapperScanner父類ClassPathBeanDefinitionScanner的doScan方法,doScan方法在掃描到basePackage中的所有介面之后,會呼叫ClassPathBeanDefinitionScanne持有的所有includeFilters的match方法,判斷該類是否滿足匹配條件,
簡單來說就是介面既要滿足被basePackage掃描的條件,又要持有MapperScan注解定義的annotationClass的注解,才會將該介面注入到SpringBoot的BeanDefinitionRegistry組件中,
4,現在回歸到我們的自定義工程中,觀察SpringBoot啟動類,通過之前的原始碼分析后,可以得出結論,SpringBoot啟動容器在加載MapperScan注解的程序中會將com.example.demo.mapper之下所有被Mapper注解修飾的介面注入到SpringBoot的BeanDefinitionRegistry組件中,最后會由SpringBoot根據BeanDefinitionRegistry組件實體化,這里我就不多加贅述,

5,這里衍生出一個問題,就是一般我們定義啟動類的MapperScan的注解,一般只會定義basePackages,而不會去配置annotationClass,那么之前第三步includeFilters集合就為空,那么匹配的條件就是被basePackage掃描的介面都會注入到SpringBoot的BeanDefinitionRegistry組件中,

二、啟動類不定義MapperScan注解的方式
1,在MybatisAutoConfiguration類中,存在一個內部類MapperScannerRegistrarNotFoundConfiguration,
如果SpringBoot啟動主類不定義MapperScan注解,SpringBoot的BeanDefinitionRegistry組件將不會加載MapperFactoryBean和MapperScannerConfigurer類,那么剛好符合MapperScannerRegistrarNotFoundConfiguration被加載到SpringBoot的BeanDefinitionRegistry組件的條件,而MapperScannerRegistrarNotFoundConfiguration內部類@Import了AutoConfiguredMapperScannerRegistrar類,

2,觀察AutoConfiguredMapperScannerRegistrar類,其實作了ImportBeanDefinitionRegistrar類,與MapperScannerRegistrar一樣,在SpringBoot的refresh背景關系的程序中會呼叫registerBeanDefinitions方法,

這里我將AutoConfiguredMapperScannerRegistrar的registerBeanDefinitions方法的關鍵內容貼出來,
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
由于自己的啟動類根本就沒有定義MapperScan注解,所以這里就相當于設定basePackage屬性為啟動類所在包目錄(AutoConfigurationPackages默認獲取啟動類所在的包)和annotationClass屬性為Mapper注解的MapperScannerConfigurer到BeanDefinitionRegistry組件中,
3,回到啟動類定義MapperScan注解的方式中的第三步,這里的MapperScannerConfigurer和之前的basePackage屬性和annotationClass屬性就不同了,在呼叫MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法的程序中,會將啟動類所在包目錄下所有帶有Mapper注解的介面注入到SpringBoot的BeanDefinitionRegistry組件中,
這里其實可以得出一個結論,當主工程間接依賴到其他的包時,如果想將其他包中帶有Mapper注解的介面生成Dao,那么SpringBoot啟動類的包名需要包含其他包中帶有Mapper注解的介面所在的包,
三、總結:
如果啟動類定義了MapperScan注解,那么只有使用了annotationClass屬性所定義的注解并且在basePackages屬性包下的所有介面,才會被Spring-Boot加載到SpringBoot的BeanDefinitionRegistry組件中,最終生成Dao類,
如果啟動類沒有定義MapperScan注解,那么只有使用了Mapper注解并且在啟動類所在包下的所有介面,才會被Spring-Boot加載到SpringBoot的BeanDefinitionRegistry組件中,最終生成Dao類,
__歡迎關注,后續會更新更多技術干貨,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/259457.html
標籤:java
上一篇:(Java開發面試)一篇文章帶你完整復習 Java 中鎖的相關知識 - 上
下一篇:Spring IOC詳解
