逐行阅读Spring5.X源码(八)Mybatis是如何利用MapperScan完成扫描的?
mybaits是通过@MapperScan注解完成扫描的,具体是如何完成的呢?首先看一下MapperScan的源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
...注解体省略...
}
@MapperScan上加了一个@Import注解,了解注解知识的读者都知道,@MapperScan起始就是@Import的子类。在上一篇博客中我们详细分析了注册扫描后置处理器ConfigurationClassPostProcessor
是如何完成扫描的,其中有一个重要的步骤就是解析@Import注解。mybatis就是利用了这个特性完成的扫描。
回顾以下上一篇博文ConfigurationClassPostProcessor
解析@Import注解的过程:
定位到ConfigurationClassParser
类的doProcessConfigurationClass
方法中的如下代码:
processImports(configClass, sourceClass, getImports(sourceClass), true);
这个方法就是处理配置类的@Import注解。起始上一篇博文已经分析过了,这里我们再分析一遍。
首先,通过getImports(sourceClass)
方法获取配置类上所有的@Import注解中的类。加入我们有一个配置类如下所示:
@Configuration
@ComponentScan(value = "com")
@Import(User.class)
@MapperScan("net")
public class Config extends ConfigSuperClass implements UserInterface {
@Value("${demo.name}")
private String name;
public String getName() {
return name;
}
}
getImports
方法就是拿到@Import(User.class)
上的User.class和@MapperScan("net")
父类上的MapperScannerRegistrar.class
,然后将这两个class封装成SourceClass并返回。分析一下getImports
源码:
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
这个方法中的核心方法是collectImports(sourceClass, imports, visited);
,将找到的class加入到imports集合中,然后返回。
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
//如果注解本身不是@Import,递归查看注解的父注解是否有@Import注解
collectImports(annotation, imports, visited);
}
}
//拿到@Import注解上的value值
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
总之,就是 1.拿到配置类上所有的注解 2.递归拿到所有注解的父注解 3.在所有的注解中找到@Import注解,拿到value值,放到imports集合中返回。 在本例中,此时我们拿到了两个class,一个是配置类本身的@Import注解中的User.class,另一个是@MapperScan的父注解@Import中的MapperScannerRegistrar.class。
拿到import导入的两个类后,就是真正的进入processImports
方法进行处理了,核心代码如下:
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
// 反射创建这个类的实例对象
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
//是否有实现相关Aware接口,如果有,这调用相关方法
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
// 延迟加载的ImportSelector
if (selector instanceof DeferredImportSelector) {
// 延迟加载的ImportSelector先放到List中,延迟加载
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}else {
// 普通的ImportSelector ,执行其selectImports方法,获取需要导入的类的全限定类名数组
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
//获取需要导入类的class
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归调用
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
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());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
- 首先,
if (candidate.isAssignable(ImportSelector.class))
判断导入的类是否实现了ImportSelector
接口。该接口源码如下:
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
接口只定义了一个selectImports(AnnotationMetadata importingClassMetadata)
方法,用于指定需要注册到容器中的Class名称。当配置类的@Import
注解引入了一个ImportSelector
接口实现类后,会根据selectImports
方法返回的Class名称生成BeanDefinition加载到spring容器中。
来看一个简单的示例:
public class User2 {
}
@Component
public class User implements ImportSelector ,BeanFactoryAware {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {User2.class.getName()};
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("实现了BeanFactoryAware接口");
}
}
上文中,我们在配置类上@Import(User.class)导入了User,此时拿到User的selectImports方法返回的类名,根据类名生成对应的BeanDefinition加载到容器中。在这里,把User2定义成了BeanDefinition,加载到容器中了。看源码如何实现的:
//如果实现了ImportSelector接口
if (candidate.isAssignable(ImportSelector.class)) {
// 获取User的class对象
Class<?> candidateClass = candidate.loadClass();
// 反射创建这个类的实例对象
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
//是否有实现相关Aware接口,如果有,这调用相关方法
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
// 延迟加载的ImportSelector
if (selector instanceof DeferredImportSelector) {
// 延迟加载的ImportSelector先放到List中,延迟加载
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}else {
// 普通的ImportSelector ,执行其selectImports方法,获取需要导入的类的全限定类名数组
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归调用
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
代码注释讲的很清楚,相信大家能看懂,但是有这么行代码```
ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);
判断是否有实现相关Aware接口,如果有,这调用相关方法。Aware,是感应和感知的意思。当User实现了对应的Aware接口时,此处就会User实现该接口的实现方法,此例中就是User的setBeanFactory(BeanFactory beanFactory)
方法。该方法传入了一个BeanFactory类型的参数,这个类型的有以下这些方法:
这样我们就能利用这个Bean工厂做很多很多扩展。这就是spring插件或者扩展开发的一个简单案例。
Aware是顶级接口,它有很多子接口:
但是在此处不是所有有Aware接口都可以,invokeAwareMethods源码:
public static void invokeAwareMethods(Object parserStrategyBean, Environment environment,
ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
if (parserStrategyBean instanceof Aware) {
if (parserStrategyBean instanceof BeanClassLoaderAware) {
ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
if (classLoader != null) {
((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader);
}
}
if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry);
}
if (parserStrategyBean instanceof EnvironmentAware) {
((EnvironmentAware) parserStrategyBean).setEnvironment(environment);
}
if (parserStrategyBean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader);
}
}
}
只有BeanClassLoaderAware、BeanFactoryAware 、EnvironmentAware、ResourceLoaderAware接口才能被调用。
Aware接口处理完后,就是判断User是否是延迟加载:
if (selector instanceof DeferredImportSelector)
显然,我们的User实现的ImportSelector接口,没有实现DeferredImportSelector接口,所以就不是延迟加载,DeferredImportSelector是ImportSelector的子接口。如果我们要实现延迟加载功能,让User实现DeferredImportSelector接口即可:
public class User implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("延迟加载User2");
return new String[] {User2.class.getName()};
}
}
启动spring,就会进入上面的if判断里的this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector)
方法,handle方法内会将selector再封装成DeferredImportSelectorHolder类型的对象,然后保存在数组中以后调用,调用时机在以后的实例化过程中再讲,先记住这点。
如果不是延迟加载的话,if条件不执行,执行else中的语句。String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
就是调用下面的方法,获取你的返回的类名数组。
else中的最后两行代码就是根据上一步返回的类名数组找到对应的class,然后递归。为什么要递归调用,因为你导入的类可能还有Import注解啊,那也要处理的。
- 其次,
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))
判断是否实现ImportBeanDefinitionRegistrar
接口。该接口允许我们手动封装BeanDefinition并注册到容器中。我们先修改下User类:
@Component
public class User implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("实现了ImportBeanDefinitionRegistrar接口");
}
}
其实,很小儿科,不讲了,在BeanDefinition博文中讲烂了。注意此时并没有立刻调用registerBeanDefinitions
这个方法,而是先通过
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
这行代码放到配置类对应的解析器的一个集合中,
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
new LinkedHashMap<>();
......
public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}
当配置类都处理完了后,遍历所有配置类解析器,调用这个集合中所有类的addImportBeanDefinitionRegistrar方法。
- 最后,如果以上接口都没实现。按普通配置类处理,递归调用,跟Config 配置类的处理方式一样,再来一遍上述过程。
也就是说,如果Import进来的类实现了ImportBeanDefinitionRegistrar 或 ImportSelector 接口,则继续递归处理他们生成的类,直到找到没有实现这两个接口的类,找到后就当作配置类按照Config的方式进行处理。
MapperScannerRegistrar
上面讲了User,其实MapperScannerRegistrar 也一样
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
spring会调用MapperScannerRegistrar
的registerBeanDefinitions
方法。
上面讲了一堆,就是为了说明spring是如何调用MapperScannerRegistrar
的registerBeanDefinitions
方法的。下一步就是分析registerBeanDefinitions
这个方法即可。
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//解析MapperScan具体信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
先解析MapperScan类的基础配置信息,起始就是拿到MapperScan所有方法的默认返回值。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
//扫描路径数组
String[] value() default {};
//扫描路径数组,另一种表达方式,跟上面一样
String[] basePackages() default {};
//类数组,根据类获取所在的包路径,作为基础扫描类型
Class<?>[] basePackageClasses() default {};
//名字生成器,配置路径下的类名根据此名字生成器进行命名
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
//待扫描的注解,注册基本包中具有指定注解的所有接口。注意这可以与markerInterface结合使用
Class<? extends Annotation> annotationClass() default Annotation.class;
//扫描指定的类
Class<?> markerInterface() default Class.class;
//如果spring中有多个sqlSessionTemplate,指定你要用哪一个,通常只有当您有多个数据源时才需要这样做
String sqlSessionTemplateRef() default "";
//如果spring中有多个SqlSessionFactory,指定你要用哪一个,通常只有当您有多个数据源时才需要这样做
String sqlSessionFactoryRef() default "";
//Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean mapper的创建工厂
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
//对于mapper是否懒加载
String lazyInitialization() default "";
}
然后调用registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)
方法,
第一步
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
将MapperScannerConfigurer生成一个GenericBeanDefinition类型的BeanDefinition再返回来备用,看源码:
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
//构建一个GenericBeanDefinition
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
//GenericBeanDefinition与MapperScannerConfigurer对应
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
第二步设置属性processPropertyPlaceHolders
为true
,下文讲为什么
builder.addPropertyValue("processPropertyPlaceHolders", true);
第三步找到扫描的包路径,放到basePackages集合中,如果没有配置扫描路径,则将@MapperScan所在的配置类的包路径作为基础包路径:
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
然后就将将MapperScannerConfigurer对应的GenericBeanDefinition注册到容器中
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
好像,没有扫描啊,只是做了一些配置工作,说实话,如果读者没有读过我之前关于后置处理器的文章,很难搞懂接下来我要讲的内容——自定义后置处理器何时调用? 建议先阅读《逐行阅读Spring5.X源码(五) 初探BeanFactoryPostProcessor后置处理器,难,特别难。》
MapperScannerConfigurer
我们首先要搞清楚,上文注册到容器中的MapperScannerConfigurer
到底是个什么鬼?自动扫描 将Mapper接口生成代理注入到Spring。
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
顾名思义,mapper扫描配置类,这个类实现了BeanDefinitionRegistryPostProcessor
接口! 变成了一个后置处理器。该接口只有一个postProcessBeanDefinitionRegistry
方法,spring启动过程中会主动调用MapperScannerConfigurer
的postProcessBeanDefinitionRegistry
方法。所以接下来就要分析这个方法
这个方法第一步
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
processPropertyPlaceHolders
,上文讲过这个值确实是true,所以这条判断成立,执行processPropertyPlaceHolders();
方法。此函数的官方注解如下:
/*
* BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
* PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
* fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
* definition. Then update the values.
*/
大概意思就是:BeanDefinitionRegistries会在应用启动的时候调用,并且会早于BeanFactoryPostProcessors的调用(在之前后置处理器博文中讲过,确实是 ),这就意味着PropertiesResourceConfigurers还没有被加载,所有对于属性文件的引用将会失效,为避免此种情况发生,此方法手动地找出定义的PropertyResourceConfigurers
并进行调用以以保证对于属性的引用可以正常工作。这个函数主要做以下两件事情:
- 找到所有已经注册的PropertyResourceConfigurer类型的bean。
- 模拟Spring的环境来用处理器。这里通过使用呢new DefaultlistableBeanFactory()来模拟Spring中的环境(完成处理器的调用后便失效),将映射的bean,也就是MapperScannerConfigurer类型bean注册到环境中来进行后处理器的调用。
可以先不用关注这点,这个会在mybatis源码专题中详解,下一步我们看mybatis如何扫描。
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
ClassPathMapperScanner
类继承了spring的ClassPathBeanDefinitionScanner
类,专门用来扫描注册
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
如果读者阅读过本专题之前的文章,肯定对这个类非常了解,如果没读过,可以 参考《逐行阅读Spring5.X源码(六) ClassPathBeanDefinitionScanner扫描器》 和逐行阅读Spring5.X源码(番外篇)自定义扫描器, Mybatis是如何利用spring完成Mapper扫描的
- python apschedule安装使用与源码分析
- 数据迁移过程中hive sql调优
- 词序:神经网络能按正确的顺序排列单词吗?
- 使用spark对hive表中的多列数据判重
- 如何从微信公众平台上下载关注用户(备份微信关注用户)
- 使用hive客户端java api读写hive集群上的信息
- 大数据算法设计模式(1) - topN spark实现
- redis性能调优笔记(can not get Resource from jedis pool和jedis connect time out)
- thrift例子:python客户端/java服务端
- springboot与thrift集成实现服务端和客户端
- 调用{dede:likewords}为dedecms添加相关搜索词
- 重新调整Keras中长短期记忆网络的输入数据
- Linux内存(手动释放cache)
- django中间件Middleware
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- CentOS7 fastdfs安装与测试
- CentOS7 jdk安装
- CentOS7 kafka安装
- Centos7 keepalived安装并监控mysql实现自动切换
- Centos7 mqtt集群安装
- CentOS7 mysql5.7安装并配置主主同步
- CentOS7 nginx安装并负载mysql
- CentOS7 zabbix安装并实现其它服务器服务监控报警与自动恢复
- CentOS7 Zookeeper安装
- 【STM32F429开发板用户手册】第38章 STM32F429的FMC总线应用之是32路高速IO扩展
- 【STM32F429开发板用户手册】第39章 STM32F429的FMC总线应用之SDRAM
- react项目搭建
- 深入理解Java泛型(三)-泛型擦除及其相关内容
- webpack实战——预处理器(loader)【下篇】
- JAVA位运算等运算符总结