系列文章主旨
从如何把自己的Bean注册到Spring容器开始,从该点出发,每篇只关注一个核心流程的原理、源码,再由该篇带出引申出来的其他Spring知识点,继续剖析,最终达到理解Spring核心原理的目的;
上篇内容
分析了spring如何通过内置的,bean工厂后置处理器,解析配置类,识别配置类注解@ComponentScan,然后去扫描对应的包路径,最后生成bd,注册到spring容器中;
详情:【Spring】Spring原理源码解析(二)-- Spring怎么识别@ComponentScan类并完成扫描生成BD注册到beanDefinitionMap中的;
本篇内容
从上一篇分析的spring内部实现的ConfigurationClassPostProcessor继续入手,分析spring在解析配置类的时候,又是怎么识别@Import类的原理源码;
文章图片均出自:https://www.processon.com/view/link/62f4d0aa1e0853714d4689bf
核心原理
代码示例
指定配置类初始化spring容器,方法内部会把配置类注册到spring容器中
// import示例
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyImportConfig.class);
A a = context.getBean(A.class);
System.out.println("a == " + a);
// import示例
MyImportConfig指定了几个需要被import的类
@Import(value = {
// 引入的普通配置类
ScanConfig.class,
// 引入了实现ImportSelector的类,该类可以引入其他配置类...
MyImportSelector.class,
// 引入了实现ImportBeanDefinitionRegistrar的类,该类可以注册bean到spring容器中
MyImportRegistor.class
})
public class MyImportConfig {
}
ScanConfig,指定扫描的路径,属于普通配置类
@ComponentScan(value = {
"com.zsh.demo.spring.learn.register.bean.importAnno",
"com.zsh.demo.spring.learn.register.bean.entity"})
public class ScanConfig {
}
MyImportSelector实现了ImportSelector,使用接口的方式来再次引入一个配置类
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 指定被import的类
return new String[]{
"com.zsh.demo.spring.learn.register.bean.importAnno.importSelector.ImportSelectorImportClz"};
}
}
被引入的ImportSelectorImportClz打印一句话,证明被实例化
public class ImportSelectorImportClz {
public ImportSelectorImportClz() {
System.out.println("ImportSelectorImportClz被实例化");
}
}
MyImportRegistor则是实现了ImportBeanDefinitionRegistrar,注册A类到spring容器
public class MyImportRegistor implements ImportBeanDefinitionRegistrar {
/**
* 这里可以注册bean到spring容器
* */
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(A.class);
registry.registerBeanDefinition("a", builder.getBeanDefinition());
}
}
图解
核心处理逻辑
Spring容器进行初始化时,会默认生成自己的bean工厂后置处理器ConfigurationClassPostProcessor,并注册到spring容器中,然后在执行所有的bean工厂后置处理时,就会执行到ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()方法;
在执行该方法时,会解析我们注册到Spring的配置类,本篇解析的是@Import注解的元数据信息的处理;
首先会尝试递归获取当前配置类(包括其注解)所有打上@Import的类、注解等,然后遍历一个个解析打上@Import类/注解,获取其value值,该值会指向其需要导入的配置类;
此时被导入的配置类按照三种情况进行处理:
1、该配置类实现了ImportSelector接口,即其又指定了所需要引入并解析的配置类,则实例化ImportSelector接口实现类后,调用接口方法获取import的类,再去递归解析这一个个被import的类;
2、该配置类实现了ImportBeanDefinitionRegistrar接口,则实例化实现类,后放入spring容器,后续就会调用该类的接口方法,完成类注册;
3、其它情况,则认为该类是个配置类,再次递归去解析该配置类;
进阶源码解析
spring调用内置工厂,处理配置类,定位到解析@import的入口
内部的工厂后置处理器ConfigurationClassPostProcessor,默认会在spring初始化时被调用,判断被引入的类属于配置类后(@component、@Import、@Bean、@ComponentScan、@ImportResource),就会对该类及其父类进行递归解析,源码位置是在:org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
该部分内容,详情可以参考【Spring】Spring原理源码解析(二)-- Spring怎么识别@ComponentScan类并完成扫描生成BD注册到beanDefinitionMap中的
从processConfigurationClass跟进到doProcessConfigurationClass()方法,就可以看到解析@Import注解的入口
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 忽略非相关...
// 解析@Import注解
processImports(configClass, sourceClass,
// 获得当前class的所有@Import的value集
getImports(sourceClass),
filter, true);
// 忽略非相关...
}
解析当前配置类,拿到类及注解上所有@Import类的value值集
图解
源码解析
构造imports集合,准备存储解析后的结果, org.springframework.context.annotation.ConfigurationClassParser#getImports
/**
* org.springframework.context.annotation.ConfigurationClassParser#getImports
*/
private Set<SourceClass