@Mapper注解和@MapperScan注解的关联性,以及源码分析@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 = icBeanDefinition(MapperScannerConfigurer.class);
Class<? extends Annotation> annotationClass = Class("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.StringArray("value")).filter(StringUtils::hasText).List()));
basePackages.addAll(Arrays.StringArray("basePackages")).filter(StringUtils::hasText)
.List()));
basePackages.addAll(Arrays.ClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.List()));
builder.addPropertyValue("basePackage", llectionToCommaDelimitedString(basePackages));
}
从registerBeanDefinitions⽅法不难看出,这⾥获取了MapperScanner注解定义的annotationClass和basePackages和basePackageClasses三个属性,然后注⼊了⼀个MapperScannerConfigurer的BeanDefinition到SpringBoot的BeanDefinitionRegistry组件中。
3,观察MapperScannerConfigurer类,其实现了BeanDefinitionRegistryPostProcessor接⼝,那么在Sp
ringBoot的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.scan(
ION_DELIMITERS));
}
实例化bean的三种方式这⾥的核⼼类是ClassPathMapperScanner类(ClassPathMapperScanner类为ClassPathBeanDefinitionScanner类的⼦类,⽽ClassPathBeanDefinitionScanner类是SpringBoot批量为Interface⽣产BeanDefinition⼯具类)。
⾸先这⾥会调⽤isterFilters()⽅法,这⾥会将MapperScan注解定义的annotationClass属性设置到ClassPathMapperScanner类中,然后调⽤ isterFilters(),这个⽅法的作⽤是设置⼀个TypeFilter的⽅法接⼝到这个scanner对象的includeFilters属性中。
然后调⽤scanner.scan( kenizeToStringArray(this.basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS))⽅法,这⾥最终会调⽤ClassPathMapperScanner⽗类ClassPathBeanDefinitionScanner的doScan⽅法,doScan⽅法在扫描到basePackage中的所有接⼝之后,会调⽤ClassPathBeanDefinitionScanne持有的所有includeFilters的match⽅法,判断该类是否满⾜匹配条件。
简单来说就是接⼝既要满⾜被basePackage扫描的条件,⼜要持有MapperScan注解定义的annotationCl
ass的注解,才会将该接⼝注⼊到SpringBoot的BeanDefinitionRegistry组件中。
4,现在回归到我们的⾃定义⼯程中,观察SpringBoot启动类,通过之前的源码分析后,可以得出结论,SpringBoot启动容器在加载MapperScan注解的过程中会将ample.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 = (this.beanFactory);
BeanDefinitionBuilder builder = icBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", llectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
}
由于⾃⼰的启动类根本就没有定义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类。
__欢迎关注,后续会更新更多技术⼲货。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。