@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的源码。

@Mapper注解和@MapperScan注解的关联性,以及源码分析_第1张图片

2,观察MapperScannerRegistrar类,其实现了ImportBeanDefinitionRegistrar接口,那么在SpringBoot的refresh上下文的invokeBeanDefinitionRegistryPostProcessors方法的过程中会调用MapperScannerRegistrar的registerBeanDefinitions方法,而MapperScannerRegistrar的registerBeanDefinitions方法中最主要的方法是registerBeanDefinitions方法。

 @Mapper注解和@MapperScan注解的关联性,以及源码分析_第2张图片

@Mapper注解和@MapperScan注解的关联性,以及源码分析_第3张图片

这里我将MapperScannerRegistrar的registerBeanDefinitions方法中的相关内容贴出来:

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {

  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

  Class 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组件实例化,这里我就不多加赘述。

@Mapper注解和@MapperScan注解的关联性,以及源码分析_第4张图片

5,这里衍生出一个问题,就是一般我们定义启动类的MapperScan的注解,一般只会定义basePackages,而不会去配置annotationClass,那么之前第三步includeFilters集合就为空,那么匹配的条件就是被basePackage扫描的接口都会注入到SpringBoot的BeanDefinitionRegistry组件中。

@Mapper注解和@MapperScan注解的关联性,以及源码分析_第5张图片

二、启动类不定义MapperScan注解的方式

1,在MybatisAutoConfiguration类中,存在一个内部类MapperScannerRegistrarNotFoundConfiguration。

如果SpringBoot启动主类不定义MapperScan注解,SpringBoot的BeanDefinitionRegistry组件将不会加载MapperFactoryBean和MapperScannerConfigurer类,那么刚好符合MapperScannerRegistrarNotFoundConfiguration被加载到SpringBoot的BeanDefinitionRegistry组件的条件,而MapperScannerRegistrarNotFoundConfiguration内部类@Import了AutoConfiguredMapperScannerRegistrar类。

@Mapper注解和@MapperScan注解的关联性,以及源码分析_第6张图片

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类。

                                                                                                                                                                                                                                          __欢迎关注,后续会更新更多技术干货。

你可能感兴趣的:(技术,java,源码,mybatis,spring,boot)