本文里面涉及到的相关文章
@MapperScan
注解里面涉及到的@Import
注解解析可以查看系列文章
SpringBoot自动装配系列文章 |
---|
@EnableXXX注解+@Import轻松实现SpringBoot的模块装配 |
带你拿捏SpringBoot自动装配的核心技术?模块装配(@EnableXXX注解+@Import)+ 条件装配(@ConditionalXXX) |
深入探究Spring Boot自动配置原理及SPI机制:实现灵活的插件化开发 |
MapperFactoryBean
和MapperScannerConfigurer
相关文章
在使用Spring Boot和MyBatis整合的时候,我们经常会看到@MapperScan
这个注解,它的作用是扫描指定包下的Mapper接口,并将它们注册到Spring容器中,这样我们就可以在Service层或者Controller层直接注入Mapper接口的实例,而不需要写DAO层的实现类。那么,@MapperScan
这个注解是如何实现这个功能的呢?它背后涉及到了两个重要的类:MapperScannerRegistrar
和MapperScannerConfigurer
,它们之间有什么区别和联系呢?
一种典型的使用方式是在 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
。
在非 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 会自动配置 SqlSessionFactory
和 SqlSessionTemplate
,进一步简化配置。
MapperScannerConfigurer
则实现了 BeanDefinitionRegistryPostProcessor
接口,它在 BeanFactory 的标准初始化过程中修改内部的 bean 定义或者增加额外的 bean 定义。该类通常通过 XML 配置方式使用,它需要在 Application Context 配置中明确声明。
总体上,MapperScannerRegistrar
更适合用于 Spring Boot 或者基于 Java Config 的配置场景,而 MapperScannerConfigurer
更常用于基于 XML 配置的传统 Spring 应用环境中。若需要基于不同的环境或者个人喜好选择使用哪种方式,需要确保配置步骤的正确实施。
由于 MapperScannerConfigurer
是在 Bean 加载完成后初始化的,因此它能够更好地与 Spring 的生命周期集成。但是这也带来了一个弊端,就是在任何使用 @Autowired
注入 Mapper 前,必须确保 MapperScannerConfigurer
已经初始化完成,这可能会影响到一些早期的 Bean 的使用。
注意不同版本的
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解析可以查看我之前的文章
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解析可以查看我之前的文章
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的奥秘
这三个类之间的关系可以用下图表示:
@MapperScan -> MapperScannerRegistrar -> MapperScannerConfigurer -> ClassPathMapperScanner -> MapperFactoryBean -> MapperProxy
通过这个流程,我们就可以实现Mapper接口的自动扫描和注入,简化了持久层的开发,提高了开发效率。当然,这个流程还涉及到了很多其他的细节和原理,比如MyBatis的MapperProxyFactory和MapperProxy类,Spring的ImportBeanDefinitionRegistrar和BeanDefinitionRegistryPostProcessor接口,以及Spring和MyBatis的整合原理等。相关文章可以参考我开头列举的系列文章,更为系统的进行学习!!!