源码透析MapperScannerRegistrar和MapperScannerConfigurer的区别及作用

文章目录

    • 前言
    • MapperScannerRegistrar
      • 使用方式
      • 实现原理
    • MapperScannerConfigurer
      • 使用方式
      • 实现原理
    • 两者区别对比
    • 源码解析
      • MapperScannerRegistrar
      • MapperScannerConfigurer
      • MapperFactoryBean
    • 总结

本文里面涉及到的相关文章

@MapperScan注解里面涉及到的@Import注解解析可以查看系列文章

SpringBoot自动装配系列文章
@EnableXXX注解+@Import轻松实现SpringBoot的模块装配
带你拿捏SpringBoot自动装配的核心技术?模块装配(@EnableXXX注解+@Import)+ 条件装配(@ConditionalXXX)
深入探究Spring Boot自动配置原理及SPI机制:实现灵活的插件化开发

MapperFactoryBeanMapperScannerConfigurer相关文章

MyBatis系列相关相关文章
究竟FactoryBean是什么?深入理解Spring的工厂神器
超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?
Mybatis与Spring结合深探——MapperFactoryBean的奥秘
Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析
源码透析MapperScannerRegistrar和MapperScannerConfigurer的区别及作用

在这里插入图片描述

前言

在使用Spring Boot和MyBatis整合的时候,我们经常会看到@MapperScan这个注解,它的作用是扫描指定包下的Mapper接口,并将它们注册到Spring容器中,这样我们就可以在Service层或者Controller层直接注入Mapper接口的实例,而不需要写DAO层的实现类。那么,@MapperScan这个注解是如何实现这个功能的呢?它背后涉及到了两个重要的类:MapperScannerRegistrarMapperScannerConfigurer,它们之间有什么区别和联系呢?

MapperScannerRegistrar

使用方式

一种典型的使用方式是在 Spring Boot 的启动类或者配置类上添加 @MapperScan 注解:

@Configuration
@MapperScan(basePackages = "com.apple.mapper")
public class MyAppConfig {
    // ...
}

点开@MapperScan注解,我们又可以看到它使用 了@Import

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

关于@Import的使用解析,我再之前的文章已有提到


@EnableXXX注解+@Import轻松实现SpringBoot的模块装配

@EnableXXX注解+@Import轻松实现SpringBoot的模块装配

@EnableXXX注解+@Import轻松实现SpringBoot的模块装配


实现原理

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

MapperScannerRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,我们在@MapperScan注解中使用 @Import 注解把它导入到配置类中,在运行时由 Spring 处理 @Import 标注的类,从而实现动态添加 BeanDefinition 到 Spring 容器的目的。

在实际的应用中, @MapperScan 注解结合 MapperScannerRegistrar 一起工作。@MapperScan 负责定义扫描路径,而 MapperScannerRegistrar 负责将这些配置注册成 BeanDefinition

MapperScannerConfigurer

使用方式

在非 Spring Boot 的 Spring 应用或者希望通过 XML 来配置扫描路径的场合更常见。以下是一个 XML 配置示例:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.apple.mapper" />
     <!-- 其他可配置项,例如 sqlSessionFactoryBean 名称 -->
</bean>

通过配置 basePackage 属性,我们告诉 MapperScannerConfigurer 要扫描哪个包。

而实际上,当使用 Spring boot,并结合 @MapperScan 注解配置 MapperScannerRegistrar 的时候,Mybatis-spring-boot-starter 会自动配置 SqlSessionFactorySqlSessionTemplate,进一步简化配置。

实现原理

MapperScannerConfigurer 则实现了 BeanDefinitionRegistryPostProcessor 接口,它在 BeanFactory 的标准初始化过程中修改内部的 bean 定义或者增加额外的 bean 定义。该类通常通过 XML 配置方式使用,它需要在 Application Context 配置中明确声明。

两者区别对比

总体上,MapperScannerRegistrar 更适合用于 Spring Boot 或者基于 Java Config 的配置场景,而 MapperScannerConfigurer 更常用于基于 XML 配置的传统 Spring 应用环境中。若需要基于不同的环境或者个人喜好选择使用哪种方式,需要确保配置步骤的正确实施。

由于 MapperScannerConfigurer 是在 Bean 加载完成后初始化的,因此它能够更好地与 Spring 的生命周期集成。但是这也带来了一个弊端,就是在任何使用 @Autowired 注入 Mapper 前,必须确保 MapperScannerConfigurer 已经初始化完成,这可能会影响到一些早期的 Bean 的使用。

源码解析

MapperScannerRegistrar

在这里插入图片描述

注意不同版本的mybatis-spring 整合依赖包里面对应的MapperScannerRegistrar会有所区别,但是做的事情都是一样的!

MapperScannerRegistrar是一个实现了ImportBeanDefinitionRegistrar接口的类,它的作用是在Spring容器启动的时候,根据@MapperScan注解的配置,动态地注册Mapper接口的BeanDefinition对象到Spring容器中。它的核心方法是registerBeanDefinitions,它的代码如下:

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  // 获取@MapperScan注解的所有属性值
  AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  if (mapperScanAttrs != null) {
    // 注册MapperScannerConfigurer的BeanDefinition对象
    registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
  }
}

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

  // 创建一个GenericBeanDefinition对象,用来存储MapperScannerConfigurer的相关属性
  GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
  beanDefinition.setBeanClass(MapperScannerConfigurer.class);
  beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  beanDefinition.setSynthetic(true);

  // 获取@MapperScan注解的各个属性值,包括basePackages, annotationClass, markerInterface, factoryBean等
  AnnotationAttributes attributes = AnnotationAttributes.fromMap(annoMeta.getAnnotationAttributes(MapperScan.class.getName()));
  List<String> basePackages = new ArrayList<>();
  basePackages.addAll(Arrays.stream(attributes.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
  basePackages.addAll(Arrays.stream(attributes.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
  basePackages.addAll(Arrays.stream(attributes.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));

  // 将@MapperScan注解的属性值赋给MapperScannerConfigurer对象的属性
  beanDefinition.getPropertyValues().add("processPropertyPlaceHolders", true);
  beanDefinition.getPropertyValues().add("annotationClass", attributes.getClass("annotationClass"));
  beanDefinition.getPropertyValues().add("markerInterface", attributes.getClass("markerInterface"));
  beanDefinition.getPropertyValues().add("factoryBean", attributes.getClass("factoryBean"));
  beanDefinition.getPropertyValues().add("basePackages", basePackages);

  // 将MapperScannerConfigurer的BeanDefinition对象注册到Spring容器中,beanName是根据@MapperScan注解所在的类的名称和序号生成的
  registry.registerBeanDefinition(beanName, beanDefinition);

}

从上面的代码可以看出,MapperScannerRegistrar的作用是创建一个MapperScannerConfigurer的BeanDefinition对象,并将它注册到Spring容器中,同时将@MapperScan注解的属性值赋给MapperScannerConfigurer对象的属性,这样就完成了@MapperScan注解的解析和注册工作。那么,MapperScannerConfigurer又是什么呢?

MapperScannerConfigurer

更为详细的MapperScannerConfigurer解析可以查看我之前的文章


Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析

Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析

Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析


MapperScannerConfigurer是一个实现了BeanDefinitionRegistryPostProcessor接口的类,它的作用是在Spring容器初始化完成后,扫描指定包下的Mapper接口,并将它们创建成MapperFactoryBean的BeanDefinition对象,然后注册到Spring容器中。它的核心方法是postProcessBeanDefinitionRegistry,它的代码如下:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  // 如果已经执行过,就跳过
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }

  // 创建一个ClassPathMapperScanner对象,用来扫描指定包下的Mapper接口
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

  // 设置扫描器的相关属性,包括注解过滤器,基础包,工厂类等
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  scanner.setMapperFactoryBeanCustomizer(this.mapperFactoryBeanCustomizer);

  // 执行扫描操作,将扫描到的Mapper接口创建成MapperFactoryBean的BeanDefinition对象,并注册到Spring容器中
  scanner.registerFilters();
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackages, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

从上面的代码可以看出,MapperScannerConfigurer的作用是创建一个ClassPathMapperScanner对象,并设置它的相关属性,然后调用它的scan方法,扫描指定包下的Mapper接口,并将它们创建成MapperFactoryBean的BeanDefinition对象,并注册到Spring容器中。这样,当Spring容器初始化完成后,就可以根据这些BeanDefinition对象,创建出Mapper接口的代理对象,并注入到其他的Bean中,实现Mapper接口的自动注入功能。那么,MapperFactoryBean又是什么呢?它又有什么作用呢?我们接下来看看。

更为详细的Reconfigurer解析可以查看我之前的文章


Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析

Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析

Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析


MapperFactoryBean

更为详细的MapperFactoryBean解析可以查看我之前的文章


Mybatis与Spring结合深探——MapperFactoryBean的奥秘

Mybatis与Spring结合深探——MapperFactoryBean的奥秘

Mybatis与Spring结合深探——MapperFactoryBean的奥秘


在这里插入图片描述

MapperFactoryBean是一个实现了FactoryBean接口的类,它的作用是创建Mapper接口的代理对象,并将它返回给Spring容器。它的核心方法是getObject,它的代码如下:

@Override
public T getObject() throws Exception {
  // 如果已经创建过代理对象,就直接返回
  if (this.mapperProxy != null) {
    return this.mapperProxy;
  }

  // 从SqlSessionTemplate或者SqlSessionFactory中获取SqlSession对象
  SqlSession sqlSession = getSqlSession();

  // 创建Mapper接口的代理对象,使用了MyBatis的MapperProxyFactory类
  this.mapperProxy = new MapperProxyFactory<>(this.mapperInterface).newInstance(sqlSession);
  return this.mapperProxy;
}

从上面的代码可以看出,MapperFactoryBean的作用是从SqlSessionTemplate或者SqlSessionFactory中获取SqlSession对象,然后使用MyBatis的MapperProxyFactory类,创建Mapper接口的代理对象,并将它返回给Spring容器。这样,当我们在Service层或者Controller层注入Mapper接口的实例时,实际上注入的是MapperFactoryBean创建的代理对象,这个代理对象会拦截Mapper接口的方法调用,并执行相应的SQL语句,完成持久层的操作。

更为详细的MapperFactoryBean解析可以查看我之前的文章


Mybatis与Spring结合深探——MapperFactoryBean的奥秘

Mybatis与Spring结合深探——MapperFactoryBean的奥秘

Mybatis与Spring结合深探——MapperFactoryBean的奥秘


总结

  • MapperScannerRegistrar是一个实现了ImportBeanDefinitionRegistrar接口的类,它的作用是在Spring容器启动的时候,根据@MapperScan注解的配置,动态地注册MapperScannerConfigurer的BeanDefinition对象到Spring容器中,同时将@MapperScan注解的属性值赋给MapperScannerConfigurer对象的属性。
  • MapperScannerConfigurer是一个实现了BeanDefinitionRegistryPostProcessor接口的类,它的作用是在Spring容器初始化完成后,扫描指定包下的Mapper接口,并将它们创建成MapperFactoryBean的BeanDefinition对象,然后注册到Spring容器中。
  • MapperFactoryBean是一个实现了FactoryBean接口的类,它的作用是创建Mapper接口的代理对象,并将它返回给Spring容器。

这三个类之间的关系可以用下图表示:

@MapperScan -> MapperScannerRegistrar -> MapperScannerConfigurer -> ClassPathMapperScanner -> MapperFactoryBean -> MapperProxy

通过这个流程,我们就可以实现Mapper接口的自动扫描和注入,简化了持久层的开发,提高了开发效率。当然,这个流程还涉及到了很多其他的细节和原理,比如MyBatis的MapperProxyFactory和MapperProxy类,Spring的ImportBeanDefinitionRegistrar和BeanDefinitionRegistryPostProcessor接口,以及Spring和MyBatis的整合原理等。相关文章可以参考我开头列举的系列文章,更为系统的进行学习!!!