@Mapper评论和@MapperScan评论是我们使用mybatis-spring的一般性评论,以前为了探索两个评论的关联性,我读了百度的文章,把@Mapper评论和@MapperScan评论翻译成干脆,自己把我的mybatis-spring和spring-boot的源代码分析结合起来,探索两个评论的关联性。
整篇文章以MapperScannerRegistrar、MybatisAutoConfiguration、autoconfiguredmapperscannerregistrar三个类为核心类进行了分析。
结论已经发表在文章的底部。
另一方面,启动类定义MapperScan注释的方式1、入口必须从@MapperScan注释说起
@MapperScan注释的内容显示,当SpringBoot主类定义MapperScan注释时,MapperScannerRegistrar类将BeanDefinition作为SpringBoot的BeanDefinition 感兴趣的人可以看到SpringBoot加载了BeanDefinitionRegistry和BeanDefinitionRegistry并实例化了Bean的源代码。
2 )观察实现ImportBeanDefinitionRegistrar接口的MapperScannerRegistrar类,在中, 在SpringBoot刷新上下文的invokebeandefinitionregistrypostprocessors方法的中途调用了MapperScannerRegistrar的registerBeanDefinitions方法MapperScannerRegistrar的registerBeanDefinitions方法中最主要的方法是registerBeanDefinitions方法。
在此处粘贴MapperScannerRegistrar的registerBeanDefinitions方法的内容。
语音识别服务(annotationattributesannoattrs,beandefinitionregistry, string beanname (beandefinitionbuilderbuilder=beandefinitionbuilder.genericbeandefinition (mapperscannerconfigurer.clicurer extendsannotationannotationclass=anno attrs.getclass (annotation class ); if (! annotation.class.equals (annotationclass ) ) builder.addpropertyvalue )、annotation class ); } liststringbasepackages=new ArrayList (; base packages.addall (arrays.stream (anno attrs.getstring array (' value ' ) ).filter (string utils 3360: hastext base packages.addall (arrays.stream (anno attrs.getstring array (' base packages ' ) ).filter (string utils 3360360 haaa Arrays.stream,annoattrs.getclassarray,' basepackageclasses ' ).map,class utils 33603360 g builder.add property value () registry.registerbeandefinition (beanname,builder.getBeanDefinition ); 从registerBeanDefinitions方法中可以看到,这里获取MapperScanner注释中定义的annotationClass、basePackages和basePackageClasses这三个属性,然后使用mapper
3 )观察了映射器配置器类,实现了Bean
DefinitionRegistryPostProcessor接口,那么在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类为ClassPathBeanDefinitionScanner类的子类,而ClassPathBeanDefinitionScanner类是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类。
__欢迎关注,后续会更新更多技术干货。