Mapper 扫描需要依赖 Maybtis/Spring 这个项目。
Mapper 扫描依赖两种方式:
- 通过 @Mapper 注解 (想通过该注解实现扫描 Mapper ,需要依赖 mybatis/spring-boot-starter 这个项目)
- 通过 @MapperScan 注解
无论是 @Mapper 还是 @MapeprScan 注解,底层都是需要去注册一个 MapperScannerConfigurer 的 Bean , 然后通过该 Bean 来实现 Mapper 的主动注入。
@Mapper 注解
@Mapper 注解主要使用在 Mapper 接口上,如果要实现只对 @Mapper 注解的接口实现扫描,则需要引入 mybatis/spring-boot-starter 这个项目。
mybatis/spring-boot-starter
使用方法请参考:https://github.com/mybatis/spring-boot-starter/blob/master/mybatis-spring-boot-autoconfigure/src/site/zh/markdown/index.mdAutoConfiguredMapperScannerRegistrar
则是自动扫描 @Mapper 注解接口的实现类。
但是该类启用的条件,必须是 Spring 没有找到 MapperScannerConfigurer
和 MapperFactoryBean
的 bean 。
源码分析
AutoConfiguredMapperScannerRegistrar
是 MybatisAutoConfiguration
的静态内部类,故这里展示的是 MybatisAutoConfiguration
的源码。
/**
* MybatisAutoConfiguration 是一个配置类
* 启用该配置类需要项目存在 SqlSessionFactory 和 SqlSessionFactoryBean 这两个类(这两个类都在 mybatis/spring 项目中)
* mybatis/spring-boot-starter 会自动引入 mybatis/spring
*/
@org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
// 忽略部分源码,这里只展示要讨论的部分
/**
* 对 @Mapper 注解自动扫描的实现类
* 它继承了 ImportBeanDefinitionRegistrar 接口,实现了 registerBeanDefinitions() 方法
* registerBeanDefinitions() 方法主要是创建并注册 MapperScannerConfigurer 的 BeanDefinition
*/
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 如果没有配置自动扫描的包路径,那么则终止扫描 Mapper
if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}
logger.debug("Searching for mappers annotated with @Mapper");
// 获取自动扫描的路径
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}
// 创建一个 MapperScannerConfigurer 的 BeanDefinitionBuilder
// 并且设置 MapperScannerConfigurer 的字段,如 processPropertyPlaceHolders、annotationClass、basePackage 等字段
// 后续 MapperScannerConfigurer 就会通过这些字段信息去扫描包
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// annotationClass 表示扫描的包中必须要有指定的注解,这里指定要有 Mapper 这个注解
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
// Need to mybatis-spring 2.0.2+
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
}
if (propertyNames.contains("defaultScope")) {
// Need to mybatis-spring 2.0.6+
builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
}
// 默认设置 sqlSessionTemplateBeanName 的值为 sqlSessionTemplate
boolean injectSqlSession = environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class,
Boolean.TRUE);
if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
Optional<String> sqlSessionTemplateBeanName = Optional
.ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));
Optional<String> sqlSessionFactoryBeanName = Optional
.ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));
if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) {