配置类
@Configuration
@ComponentScan("com.scorpios")
@MapperScan("com.scorpios.mapper")
public class AppConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return dataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
}
启动类
public static void main( String[] args )
{
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
StudentMapper studentMapper = (StudentMapper) ac.getBean("studentMapper");
System.out.println(studentMapper.selectAll());
}
1. 处理import过程
Spring源码系列(四)——ConfigurationClassPostProcessor功能解析
https://blog.csdn.net/zxd1435513775/article/details/120935494?spm=1001.2014.3001.5501
在上面这篇文章中,我们分析了parser.parse(candidates);这个解析配置类的方法,也重点分析了Spring是如何解析并扫描@ComponentScan注解的,这部分核心代码主要在doProcessConfigurationClass()方法中。
当Spring扫描完@ComponentScan注解后,就去调用下面这行代码processimports()来处理@import注解导入的类。
// 处理 @import 注解 processimports(configClass, sourceClass, getimports(sourceClass), true);
我们按照上面的启动类和配置类来运行程序,并断点到processimports()方法这一行,如下图。这个sourceClass是AppConfig.class配置类,getimports(sourceClass)方法是拿到这个配置类上面的@import注解信息。
从断点处可以看到,拿到了@MapperScan("com.scorpios.mapper")注解中的@import(MapperScannerRegistrar.class)信息,MapperScannerRegistrar 实现 importBeanDefinitionRegistrar接口。
public class MapperScannerRegistrar implements importBeanDefinitionRegistrar
我们知道@import注解的使用有三种方式,其中有一种使用方式是导入importBeanDefinitionRegistrar实现类。
@import注解就是把Class类转化为BeanDefinition,然后把这个BeanDefinition添加到Spring容器中
关于@import注解的使用可以看下面这篇文章:
https://blog.csdn.net/zxd1435513775/article/details/100625879
向Spring容器中注册组件的几种方式
下面来看下代码中处理@import注解的方法逻辑:
private void processimports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection importCandidates, boolean checkForCircularimports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularimports && isChainedimportOnStack(configClass)) {
this.problemReporter.error(new CircularimportProblem(configClass, this.importStack));
} else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(importSelector.class)) {
// Candidate class is an importSelector -> delegate to it to determine imports
// 处理importSelector类
Class> candidateClass = candidate.loadClass();
importSelector selector = BeanUtils.instantiateClass(candidateClass, importSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredimportSelectors != null && selector instanceof DeferredimportSelector) {
this.deferredimportSelectors.add(
new DeferredimportSelectorHolder(configClass, (DeferredimportSelector) selector));
} else {
String[] importClassNames = selector.selectimports(currentSourceClass.getmetadata());
Collection importSourceClasses = asSourceClasses(importClassNames);
processimports(configClass, currentSourceClass, importSourceClasses, false);
}
} else if (candidate.isAssignable(importBeanDefinitionRegistrar.class)) {
// 处理importBeanDefinitionRegistrar类
// importBeanDefinitionRegistrar类暴露出BeanDefinitionRegistry
// 这个BeanDefinitionRegistry就是可以向spring容器中添加BeanDefinition
Class> candidateClass = candidate.loadClass();
// 实例化,创建出来对象,并不需要放到bdMap中,后面直接调用,Mybatis与Spring集成就是在此处引入的
importBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, importBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry);
configClass.addimportBeanDefinitionRegistrar(registrar, currentSourceClass.getmetadata());
} else {
// 处理普通类
// Candidate class not an importSelector or importBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerimport(currentSourceClass.getmetadata(), candidate.getmetadata().getClassName());
// 此处又是一个递归调用,把import进来的类再次进行解析
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
} catch (BeanDefinitionStoreException ex) {
throw ex;
} catch (Throwable ex) {
} finally {
this.importStack.pop();
}
}
}
上面处理importBeanDefinitionRegistrar类的方法只有四行:
- 获取到org.mybatis.spring.annotation.MapperScannerRegistrar类
- 实例化MapperScannerRegistrar
- 把实例化好的MapperScannerRegistrar放入到由AppConfig转化的ConfigurationClass类中的importBeanDefinitionRegistrars属性中,后面会从这个Map中拿取并执行
2. 执行import导入类注意:此处MapperScannerRegistrar创建出来对象,并没有放到bdMap中,而是放到了ConfigurationClass类中的importBeanDefinitionRegistrars属性中,后面会直接调用,Mybatis与Spring集成就是在此!!!!
AOP的importBeanDefinitionRegistrar也是在此导入!!!
上面完成了对importBeanDefinitionRegistrars接口类的扫描工作,并也已经实例化,但我们知道,并没有把这些类转为BeanDefinition,而是直接进行了实例化。==那在哪里进行实现这个接口类方法的调用呢?==答案是在parser.parse(candidates);这行代码的下面第9行,就是下图打断点的地方。this.reader.loadBeanDefinitions()这个方法中进行了调用。
从上面的分析中,我们知道,实现importBeanDefinitionRegistrars接口的类会被放到ConfigurationClass的Map属性的importBeanDefinitionRegistrars中,上面这个断点处,就是要去这个Map中拿出这些接口实现类,逐个遍历,并进行方法调用。
下面来看下这个方法的处理逻辑:
public void loadBeanDefinitions(SetconfigurationModel) { TrackedConditionevaluator trackedConditionevaluator = new TrackedConditionevaluator(); for (ConfigurationClass configClass : configurationModel) { // 具体看下这个方法 loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionevaluator); } } private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionevaluator trackedConditionevaluator) { if (trackedConditionevaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeimportingClass(configClass.getmetadata().getClassName()); return; } // 如果一个类是被import,会被spring标注,在这里完成注册 if (configClass.isimported()) { registerBeanDefinitionForimportedConfigurationClass(configClass); } // 此处是处理配置类中@Bean注解修饰的方法 for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } // 处理配置类上@importResource导入的资源 ,xml中的bean loadBeanDefinitionsFromimportedResources(configClass.getimportedResources()); // 处理配置类上@import导入的importBeanDefinitionRegistrar接口实现类 // 调用importBeanDefinitionRegistrar接口中的registerBeanDefinitions()方法 loadBeanDefinitionsFromRegistrars(configClass.getimportBeanDefinitionRegistrars()); }
上面是处理配置类中具有@Bean注解的方法,下面就是来处理实现importBeanDefinitionRegistrar接口类的方法了。Mybatis整合的入口就在这里!!!!
二、Mybatis的入口类MapperScannerRegistrar从上面的分析顺下来,程序调用到了MapperScannerRegistrar类中的registerBeanDefinitions()方法,这个类是由Mybatis提供的,也是Spring整合Mybatis的入口。
// 注意下第二个参数BeanDefinitionRegistry
public void registerBeanDefinitions(Annotationmetadata importingClassmetadata, BeanDefinitionRegistry registry) {
// 此处要注意下根据MapperScan注解,返回的annoAttrs的值
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassmetadata.getAnnotationAttributes(MapperScan.class.getName()));
// 创建一个ClassPathMapperScanner扫描器,这个扫描器继承ClassPathBeanDefinitionScanner
// 这个ClassPathBeanDefinitionScanner扫描器熟悉吧,Spring完成扫描工作就是用的这个扫描器
// 注意一下,此处的扫描器是拥有spring容器的,即registry,这样就可以往spring容器中放东西了
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 下面一系列代码都是为这个scanner赋值一些属性
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List basePackages = new ArrayList();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
// 开始扫描@MapperScan("com.scorpios.mapper")指定的路径
scanner.doScan(StringUtils.toStringArray(basePackages));
}
此处要注意下根据@MapperScan注解,返回的annoAttrs的值,其中factoryBean的属性值是个FactoryBean
上面方法的最后一行scanner.doScan()方法,打个断点看下,这个scanner扫描器里面的具体情况:
1. scanner.doScan()注意,此处的scanner扫描器是拥有Spring容器的,即registry,这样就可以把扫描到类转化为BeanDefinition,然后放入到Spring容器中了
public SetdoScan(String... basePackages) { // 调用父类的doScan()方法,此时basePackages是@MapperScan注解所指定的包 // 将扫描到的class转化为BeanDefinition返回 Set beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { // 打印代码略 } else { // 处理扫描出来并转化成的BeanDefinition processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
由上面的分析知道,这个ClassPathMapperScanner扫描器,继承ClassPathBeanDefinitionScanner,所以此处调用父类的doScan()方法就是调用ClassPathBeanDefinitionScanner类中的doScan()方法。
再仔细一看这个ClassPathBeanDefinitionScanner,不就是Spring自己扫描包时候的扫描器么?确实是的,这地方连调用的方法都是一样的。所以此处又把Spring的包扫描流程走了一遍,只不过扫描包的路径是@MapperScan注解指定的值了。
那么问题来了?
既然此处又把Spring包扫描的流程走了一遍,那为什么在第一次扫描的时候,没有把这些Mapper文件扫描进去呢?那Spring进行包扫描的时候是怎么判断一个类是否符合条件呢?下面来分析一下。
下面来逐遍调试,断点打在ClassPathScanningCandidateComponentProvider#scanCandidateComponents()方法。
下面这张图是Spring第一次自己扫描com.scorpios包的结果。我们可以看到,会把com.scorpios包下面的所有类都拿到放到这个Resource数组中,然后逐个resource去遍历,看看是否满足条件转化为BeanDefinition
下面来看看是如何进行判断是不是符合将类转化为BeanDefinition的,方法就是上图打断点的地方,isCandidateComponent(),下面来看下这个方法代码:
protected boolean isCandidateComponent(metadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
// 是否在排除范围之内,满足直接返回
if (tf.match(metadataReader, getmetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
// 如果匹配成功,就标记为满足条件,然后把类转为BeanDefinition
if (tf.match(metadataReader, getmetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
下面要来看一下这个TypeFilter,因为两次扫描的区别就在这!!!!!
this.excludeFilters中match()的判断是在AbstractTypeHierarchyTraversingFilter#match()方法。
matchSelf()方法也是在AbstractTypeHierarchyTraversingFilter类中,这个方法直接返回的是false,表示不满足。
protected boolean matchSelf(metadataReader metadataReader) {
return false;
}
this.includeFilters中match()的判断也是在AbstractTypeHierarchyTraversingFilter#match()方法。
但此处的matchSelf()方法是在AnnotationTypeFilter类中
// 此处的判断就是类上是否有@Component注解,如果有,则返回true
protected boolean matchSelf(metadataReader metadataReader) {
// 拿到注解信息,metadata存放的就是注解信息
Annotationmetadata metadata = metadataReader.getAnnotationmetadata();
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considermetaAnnotations && metadata.hasmetaAnnotation(this.annotationType.getName()));
}
这里知道为什么Spring第一次扫描能扫描到哪些类了吧。
下面再来分析下从Mybatis中第二次触发的包扫描:
扫描的包路径是com.scorpios.mapper,即接口文件所在的位置。从上图中可以看出来拿到了Mapper文件。
下面要来验证下这个文件是否需要转化为BeanDefinition
下面又到了match()方法,但此时的match()方法是在ClassPathMapperScanner类中,而且代码中是返回的true,所以此处的所有扫描文件都满足,都会被转化为BeanDefinition
此处需要注意的是,BeanDefinition里面的beanClass属性现在是com.scorpios.mapper.StudentMapper
但后面会被替换为org.mybatis.spring.mapper.MapperFactoryBean
这个替换的地方在哪呢?
答案在ClassPathMapperScanner类中的processBeanDefinitions()
为什么需要替换呢?
2. processBeanDefinitions()// 这个方法的入参是扫描到的Mapper文件转化为BeanDefinition的集合 private void processBeanDefinitions(SetbeanDefinitions) { GenericBeanDefinition definition; // 循环扫描到的BeanDefinition for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); // 映射器接口是bean的原始类,但是bean的实际类是MapperFactoryBean definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 // 将扫描到的所有Mapper类转化为BeanDefinition中的class设置为MapperFactoryBean definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } 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; } // 设置自动注入的类型,按type进行注入 if (!explicitFactoryUsed) { definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }
到此处就分析完了,Spring是如何整合Mybatis框架的了,而且在什么地方进行了从接口到类的替换。
三、小结Spring整合Mybatis主要就是通过@MapperScan注解,该注解背后的原理就是通过@import向容器中导入MapperScannerRegistrar类,而该类实现了importBeanDefinitionRegistrar接口。
本文通过分析@import注解是如何处理实现importBeanDefinitionRegistrar接口的类源代码,来熟悉Mybatis的整合过程,并知道了将接口替换为MappperFactoryBean的位置。
其实,Spring中AOP的实现原理也是这个importBeanDefinitionRegistrar接口,留在下篇分析吧。



