@Mapper注解和@MapperScan注解是我们使用mybatis-spring的常用注解,之前为了探究两个注解的关联性,百度了一波文章,但是都将@Mapper注解和@MapperScan注解分开讲解。索性自己结合mybatis-spring和spring-boot源码分析,探究两个注解的关联性。
整篇文章以MapperScannerRegistrar、MybatisAutoConfiguration、AutoConfiguredMapperScannerRegistrar三个类作为核心类分析
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 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组件中。
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 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类。
__欢迎关注,后续会更新更多技术干货。