Mybatis的Mapper扫描机制:@MapperScan源码
通过@Import注解导入了MapperScannerRegistrarMapperScannerRegistrar向容器中注入是一个BeanDefinitionRegistryPostProcessor,将会被Spring容器回调方法在方法中创建了,并且调用ClassPathMapperScanner的scan方法重写了父类ClassPathBeanDefinitionScanner的doSca
文章目录
1. @MapperScan的用处
在传统的Mybatis开发中
- 通过
SqlSession#getMapper
来得到Mapper - 然后使用Mapper进行数据库访问
而在Mybatis集成Spring的开发中,只需要给配置类上标注@MappperScan("指定包名")
即可将Mapper注入到Spring的容器中
例如:
@SpringBootApplication
@MapperScan("com.zzzj.dao")
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args)
}
}
2. 概要
@MapperScan
和@ComponentScan
一样,都是基于ClassPathBeanDefinitionScanner
实现的
只不过@MapperScan
做了一些额外的处理,将扫描出来的BeanDefinition
替换了,来了一手狸猫换太子
如果还不熟悉ClassPathBeanDefinitionScanner
可以查看这篇文章:Spring的Bean扫描机制:ClassPathBeanDefinitionScanner源码
3. 源码追踪
3.1 @Import(MapperScannerRegistrar.class)
@MapperScan
注解上使用@Import
注解导入了MapperScannerRegistrar
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
// ...
}
MapperScannerRegistrar
向Spring容器中导入了MapperScannerConfigurer
3.2 MapperScannerConfigurer
MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
接口,将在BeanDefinitionRegistry被Spring创建好后被回调
回调postProcessBeanDefinitionRegistry
方法
此时的流程如下图所示
3.3 postProcessBeanDefinitionRegistry
接下来跟踪MapperScannerConfigurer的postProcessBeanDefinitionRegistry
方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 忽略
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 1. 创建scanner, { ClassPathMapperScanner } 继承了 { ClassPathBeanDefinitionScanner }
// { this.$propertyName } 来自@MapperScan注解的属性
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));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
// 调用scan方法, 扫描包下的类
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
3.3 scan
上面有提到,ClassPathMapperScanner
继承自ClassPathBeanDefinitionScanner
ClassPathMapperScanner
并没有重写scan
方法,这里还是调用的ClassPathBeanDefinitionScanner的scan方法
Spring的Bean扫描机制:ClassPathBeanDefinitionScanner源码
在上篇文章中已经追踪过scan方法的具体流程了,就不再赘述
但是
ClassPathMapperScanner
重写了ClassPathBeanDefinitionScanner的doScan方法
接下来继续跟追doScan方法
// ClassPathBeanDefinitionScanner 的 scan 方法源码
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
3.4 doScan
注意这里是ClassPathMapperScanner
重写的doScan
方法
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 1. 得到super ( ClassPathBeanDefinitionScanner ) 扫描后的 beanDefinition 集合
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
// 2. 对 beanDefinition 集合做额外的处理
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
为什么ClassPathMapperScanner要对扫描出来的BeanDefinition做额外的处理?
日常开发中,基本都是定义一个接口,作为Mapper
例如
public interface UserMapper {
User selectById(int id);
}
UserMapper被扫描出来后,ClassPathBeanDefinitionScanner为其创建对应的BeanDefinition
对象
BeanDefinition
对象的BeanClass就是UserMapper.class
而一个接口是无法直接被Spring实例化的,我们需要的是SqlSession.getMapper
创建出来的代理对象
ClassPathMapperScanner的额外处理就是修改BeanDefinition的属性,使其最终还是通过SqlSession.getMapper
方法创建Mapper对象
====================================================================================================
那么接下来继续追踪processBeanDefinitions
方法,看看这个方法究竟做了什么额外处理
3.5 processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
// 注意这里: 修改了BeanClass
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
// ... 忽略边缘逻辑代码
}
}
在该方法中, 最最核心的一行代码就是
definition.setBeanClass(this.mapperFactoryBeanClass);
mapperFactoryBeanClass
属性在ClassPathMapperScanner
被定义
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
// ....
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
// ....
}
也就是说,ClassPathMapperScanner 将扫描出的BeanDefinition的BeanClass替换为了MapperFactoryBean
3.6 MapperFactoryBean
MapperFactoryBean是一个FactoryBean
我们关注该类的getObject
方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
可以看到,兜兜转转,最终还是通过SqlSession.getMapper
方法来创建Mapper的
那么该对象的SqlSession
和mapperInterface
是怎么来的呢?
还是在processBeanDefinitions
方法中被赋值的
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
// 1. 通过 { MapperFactoryBean } 的构造方法注入 { mapperInterface } 属性
// beanClassName也就是被扫描的接口全限定名, 例如 "com.zzzj.UserMapper"
// 注意: MapperFactoryBean 的构造方法接收一个Class对象, 而不是String对象 ( beanClassName是String类型的 )
// 在Spring调用 { MapperFactoryBean } 构造方法前会进行值的转换
// 将String转换为Class
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(this.mapperFactoryBeanClass);
// 2. 这里赋值的 { sqlSessionTemplate }
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
// ......
}
}
4. 总结
- @MapperScan通过@Import注解导入了MapperScannerRegistrar
- MapperScannerRegistrar向容器中注入MapperScannerConfigurer
- MapperScannerConfigurer是一个BeanDefinitionRegistryPostProcessor,将会被Spring容器回调
postProcessBeanDefinitionRegistry
方法 - 在
postProcessBeanDefinitionRegistry
方法中创建了ClassPathMapperScanner,并且调用ClassPathMapperScanner的scan
方法 - ClassPathMapperScanner重写了父类ClassPathBeanDefinitionScanner的
doScan
方法,在扫描完Bean获取到BeanDefinition后,在processBeanDefinitions
方法对其进行了额外的处理 processBeanDefinitions
方法将所有的BeanDefinition的beanClass属性转换为了MapperFactoryBean- MapperFactoryBean是一个FactoryBean,将会被Spring回调
getObject()
方法,并且将方法的返回值注入到容器中 - MapperFactoryBean的
getObject()
方法通过sqlSession#getMapper
方法创建Mapper对象,注入到容器中
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)