目录
3.2. 实现了ImportBeanDefinitionRegistrar接口
4.1. AbstractApplicationContext.refresh()
4.3. 进入ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry()
4.4. 进入ConfigurationClassPostProcessor类.processConfigBeanDefinitions()
4.4.1. ConfigurationClassUtils
4.4.2. 进入ConfigurationClassParser类.parse(Set)
4.4.3. processDeferredImportSelectors()
4.5. 进入ConfigurationClassPostProcessor类的 reader.loadBeanDefinitions(configClasses)
1.前言
最近经常用到@Import注解,让我对他的底层源码实现很感兴趣,同时我衍生了一系列思考,如
1.@Configuration,@Component,@Import,@ImportResouce,@Service, @ComponentScan @PropertySource,这些注解跟@Import注解有什么本质上的异同点?@ComponentScan是属于扫描包下面的配置类并注册成beanDefinition,而@Configuration,@Component本身就是配置类,那他们解析和注册配置类的顺序又是什么?
2.在spring底层处理中是通过单纯的if-else模式,还是策略模式来处理这一系列注解?
2. 本文结构
1.四种@Import注解用法列举
2.spring源码流程图
3.分析spring源码流程并讲述四种import注解的原理以及bean注入方式
3. 三种import注解使用
3.1. @Import一个普通类
spring会将该类加载到spring容器中
public class TestClass {
public void test() {
System.out.println( "test案例方法" );
}
}
@Import(TestClass.class)
public class ImportConfig {
}
![](https://i-blog.csdnimg.cn/blog_migrate/b17ca326341314c50bf99fdfda31458c.png)
3.2. 实现了ImportBeanDefinitionRegistrar接口
在重写的registerBeanDefinitions方法里面,能拿到BeanDefinitionRegistry bd的注册器,能手工往beanDefinitionMap中注册 beanDefinition
public class TestRegistry {
public void test() {
System.out.println( "TestRegistry test()方法 案例" );
}
}
public class ImportRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry ) {
RootBeanDefinition bd = new RootBeanDefinition();
bd.setBeanClass( TestRegistry.class );
registry.registerBeanDefinition( "myClassRegistry", bd );
}
}
@Import(ImportRegistry.class)
public class ImportConfig {
}
![](https://i-blog.csdnimg.cn/blog_migrate/17958c90d3912c94d05310cf1271a7c7.png)
3.3. 实现了ImportSelector
重写selectImports方法该方法返回了String[]数组的对象,数组里面的类都会注入到spring容器当中
public class TestImport {
public void test() {
System.out.println( "TestImport test方法" );
}
}
public class TestImportSelector implements ImportSelector {
@Override
public String[] selectImports( AnnotationMetadata importingClassMetadata) {
return new String[] {TestImport.class.getName()};
}
}
@Import(TestImportSelector.class)
public class ImportConfig {
}
![](https://i-blog.csdnimg.cn/blog_migrate/1101d2f0741ca50b9ff26231dc73a464.png)
3.4. DeferredImportSelector
用于springboot的自动装配,这里不做详细描述,后面会写相关文档
3.5. 注意项
假设有@Import(A.class)
1.如果 A类实现了DeferredImportSelector接口(DeferredImportSelector是ImportSelector的子类),spring容器就会实例化A类,并且调用其selectImports方法。而DeferredImportSelector实例的selectImports方法调用时机晚于ImportSelector的实例,要等到@Configuration注解中相关的业务全部都处理完了才会调用,下面代码会说明
2.如果 A类实现了ImportBeanDefinitionRegistrar接口,那么 spring容器就会实例化A类,并且调用其registerBeanDefinitions方法
3.如果 A类没有实现ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar等其中的任何一个,那么 spring容器就不会实例化A类
4. 从Spring源码了解@Import的过程
先奉上全局流程图
看不清没关系,奉上高清链接
4.1. AbstractApplicationContext.refresh()
先请上我们的主角spring容器,它的初始化代码是AbstractApplicationContext的refresh()方法,它里面调用了invokeBeanFactoryPostProcessors(),这个是我们的入口
![](https://i-blog.csdnimg.cn/blog_migrate/6a2f81397e04de0706e6306e55e6659d.png)
4.2. invokeBeanFactoryPostProcessors()调用PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
![](https://i-blog.csdnimg.cn/blog_migrate/68346820682c46e3b17ee62736e1be02.png)
进入invokeBeanFactoryPostProcessors()方法
![](https://i-blog.csdnimg.cn/blog_migrate/bdec22b72823cc698a2487465a7385d9.png)
上面红线圈出来的意思是,只有ConfigurationClassPostProcessor类满足既实现了BeanDefinitionRegistryPostProcessor接口,又实现了PriorityOrdered接口的条件!!!
所以invokeBeanDefinitionRegistryPostProcessors()方法将调用ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法
![](https://i-blog.csdnimg.cn/blog_migrate/a1888e0012a0770b5bacfe4ebd70267f.png)
展示一下上面代码的流程图:
![](https://i-blog.csdnimg.cn/blog_migrate/aaefa9cd1685e5d5990b3507d3ab365e.png)
4.3. 进入ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry()
因为只有ConfigurationClassPostProcessor类符合条件,所以进入ConfigurationClassPostProcessor类里面
![](https://i-blog.csdnimg.cn/blog_migrate/a9d444d54f267981575617c9940af052.png)
进入后,继续看核心代码processConfigBeanDefinitions()
![](https://i-blog.csdnimg.cn/blog_migrate/9e25b1a6a9e97ce708570910061160e6.png)
4.4. 进入ConfigurationClassPostProcessor类.processConfigBeanDefinitions()
下面代码流程图:
![](https://i-blog.csdnimg.cn/blog_migrate/401ea644ecb4ddd82352d164f7b7fb41.png)
核心代码如下(下面几页代码都是同一个方法的,内容太多分开截图):
1.获取所有的beanName
2.看BeanDefinition中是否有处理完成的标识。没有处理完成的标识则在checkConfigurationClassCandidate中判断是否是候选的需要处理的BeanDefinition,并且set标识进入beanDefinition中,如果是就放入容器configCandidates。
3.创建一个配置类解析器对象,并调用parse方法解析配置类的代码,解析出来的配置类的数据放在ConfigurationClassParser类里面的属性值-Set<ConfigurationClass>中 ,且返回这个set集合,每个ConfigurationClass元素代表一个配置类
4.把set集合放入this.reader.loadBeanDefinitions(configClasses)方法,注册到BeanDefinitionMap中
![](https://i-blog.csdnimg.cn/blog_migrate/040f9809715be93d063527412787d26f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e75ecd8fd608681fde2add6a1091625d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0fcb7156e349604fc25bd117b99d28bb.png)
ConfigurationClassPostProcessor的核心代码在postProcessBeanDefinitionRegistry的processConfigBeanDefinitions中,并且processConfigBeanDefinitions是bean在实例化前调用的(postProcessBeanDefinitionRegistry在bean实例化之前执行)
4.4.1. ConfigurationClassUtils
这里先看看ConfigurationClassUtils类,他的几个核心方法被上面代码所调用,下面讲解一下这几个核心方法,代码如下:
ConfigurationClassUtils.isFullConfigurationClass()
下面两张图对比值来看
![](https://i-blog.csdnimg.cn/blog_migrate/02dc3038abbaa9604a29f4821c29ceea.png)
ConfigurationClassUtils.isLiteConfigurationClass()
下面两张图对比值来看
![](https://i-blog.csdnimg.cn/blog_migrate/12c9d0b218709dfd9c3ad4f6e707ed0d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6b89970cc218fb15eda08c36b6d09606.png)
关于full和lite的说明
这个配置类有两种情况,一种是传统意义上的带上@Configuration注解的配置类,还有一种是没有
带上@Configuration,但是带有@Component,@Import,@ImportResouce,@Service,
@ComponentScan等注解的配置类,在Spring内部把前者称为Full配置类,把后者称之为Lite配置
类。在本源码分析中,有些地方也把Lite配置类称为普通Bean。
ConfigurationClassUtils.checkConfigurationClassCandidate()
首先判断BeanDefinition是扫描注解 还是自己添加的,拿到metadata对象,metadata中包含了类的注解信息。
beanDefinition.getMetadata()这个方法要特别注意,后面代码会频繁出现,metadata获取的是beandefinition的元信息,里面包含了类的注解信息,后面要判断类的注解信息是什么,例如@Import,然后处理import里面内容
![](https://i-blog.csdnimg.cn/blog_migrate/5a0e958d73fc242f74a2e831008237d5.png)
根据metadata中的注解判断是full匹配还是lite匹配,然后set 标识符 进去
![](https://i-blog.csdnimg.cn/blog_migrate/1c0d43400c3ac14b44d90f162ad96d96.png)
isFullConfigurationCandidate()方法就是判断是不是@Configuration注解
![](https://i-blog.csdnimg.cn/blog_migrate/dfbe1a15d3b353d5205c9afc5684d961.png)
isLiteConfigurationCandidate()方法就是判断是不是Component,ComponentScan Import ImportResource和@bean 注解
![](https://i-blog.csdnimg.cn/blog_migrate/78cc4fed8b8006a28a2d819e01bf3f5e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/254113f35f65df501eb033dde5c35f69.png)
这个代码是不是很熟悉,就是前面CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))的时候获取为空,在这里才正式set进去
4.4.2. 进入ConfigurationClassParser类.parse(Set<BeanDefinitionHolder>)
![](https://i-blog.csdnimg.cn/blog_migrate/29498d613ab9d5ceab8861c2189e855f.png)
4.4.2.1. 进入parse(AnnotationMetadata , String)方法
注:这个跟上面的方法都是属于ConfigurationClassParser类
![](https://i-blog.csdnimg.cn/blog_migrate/c910dc1c92254e3141a028e5ac9d239f.png)
这里把配置类的元信息和配置类的beanName封装成ConfigurationClass对象,这个对象在后面经常用到,这个对象也代表着一个配置类,例如被@Import、@Component注解的具体的配置类
4.4.2.2. 进入processConfigurationClass()
注:这个跟上面的方法都是属于ConfigurationClassParser类
![](https://i-blog.csdnimg.cn/blog_migrate/ab63833d30fcd4f13855a1769bc5d04a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5910692e954ddfb7eb7578c7622cf806.png)
4.4.2.2.1. configurationClasses.put()
这个集合存放的是被解析的配置类的集合,后面要返回这个集合,用于注册bean
![](https://i-blog.csdnimg.cn/blog_migrate/362bff8b0e27c85fe060b2a7f7a82f34.png)
4.4.2.3. 进入doProcessConfigurationClass()
注:这个跟上面的方法都是属于ConfigurationClassParser类
下面代码流程图:
![](https://i-blog.csdnimg.cn/blog_migrate/52466b157d1fdb7c6b786d31b685f5f3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4d113b16c9e98c434a679fabc40a43d5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ab7f822edf89c9a608078892cbc10d0b.png)
这个doProcessConfigurationClass()是专门解析配置类的,这里面主要是五段逻辑
1.解析@PropertySource注解,加载properties文件创建PropertySource对象加入到Environment对象中
2.扫描@ComponentScan注解的basePackages属性的路径,并把该路径下的所有配置类解析和注册进ioc容器中
3.处理 @Import注解里的类,例如@Import(TestClass.class),那就将TestClass配置类放到processConfigurationClass递归循环处理
4.处理 @ImportResource,将locations指向的xml文件变成resources,并存到ConfigurationClass类的importedResources字段中,待后面注册
5.处理 @Bean methods,并存到ConfigurationClass类的beanMethods字段中,待后面注册
我们主要看的是第三段逻辑,调用processImports()方法
4.4.2.4. 进入processImports()
注:这个跟上面的方法都是属于ConfigurationClassParser类
这里专门解析@Import注解
![](https://i-blog.csdnimg.cn/blog_migrate/00e3f4b25006585ef458c49bf20f4a82.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2a51cdebb08aedbc796ba3bc86faa50b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d56d741ee021724dc59d00a65f1bdec6.png)
这里有三段核心的if-else逻辑
1.第一段处理实现了ImportSelector接口的,如果是子类deferredImportSelectors,那就先存到集合中,后面再处理,如果不是子类,就递归解析-- 直到成普通组件,就会走到第三段逻辑
2.第一段处理实现了ImportBeanDefinitionRegistrar接口,实现ImportBeanDefinitionRegistrar接口的bean,放到类变量configurationClasses里面去了,则调用了configurationClasses.addImportBeanDefinitionRegistrar()方法
3.处理的是没有实现ImportSelector和ImportBeanDefinitionRegistrar这些接口的普通bean,以及第一段代码递归解析的普通组件,调用processConfigurationClass()方法处理,而这个方法在前面 4.4.2.2 出现过 ,就是专门处理解析某个配置类的
下面附上这三个ConfigurationClass的属性字段
存@ImportResource解析出来的内容
![](https://i-blog.csdnimg.cn/blog_migrate/4d10bfb2cd1cb4a444405e7d7558805e.png)
存@Bean解析出来的内容
![](https://i-blog.csdnimg.cn/blog_migrate/20825e556d2a1c7fbce55d418de93412.png)
存@Import 实现了ImportBeanDefinitionRegistrar接口的内容
![](https://i-blog.csdnimg.cn/blog_migrate/46b4ccadae9b21e0d7970f02301bcbd1.png)
4.4.3. processDeferredImportSelectors()
上面已经告一段落了,ConfigurationClass里面的字段已经获取到了相应值,接下来回到 4.4.2 里面的ConfigurationClassParser类.parse(Set<BeanDefinitionHolder>) 方法
![](https://i-blog.csdnimg.cn/blog_migrate/addb12c632127f58384b04cc03f168ce.png)
4.5. 进入ConfigurationClassPostProcessor类的 reader.loadBeanDefinitions(configClasses)
还记得 4.4 段落的的ConfigurationClassPostProcessor类.processConfigBeanDefinitions()方法吗,上面我们已经讲过了这个方法里面的parser.parse()流程了,这里已经解析了配置类的信息,下面需要调用reader.loadBeanDefinitions()方法去ConfigurationClass类里的字段获取值,并注册相关的beanDefinition了。
ConfigurationClass类里的字段在 4.4段落 有列出来
![](https://i-blog.csdnimg.cn/blog_migrate/df129370424426afeb38e77176b2c202.png)
我们先看一下这个reader
![](https://i-blog.csdnimg.cn/blog_migrate/d8d35d4f546995c27caea3e7f672b387.png)
这个reader就是用来读取配置信息的,读取后,会调用他的属性值BeanDefinitionRegistry去注册beanDefinition,看这个名称就知道他是BeanDefinition的注册工具了
![](https://i-blog.csdnimg.cn/blog_migrate/082170e05de4de14fb022c07c4993468.png)
下面我们正式进入loadBeanDefinitions()方法
进入ConfigurationClassBeanDefinitionReader类的loadBeanDefinitions()
![](https://i-blog.csdnimg.cn/blog_migrate/8bf9f54f7196328c3d815aac63dac418.png)
循环ConfigurationClass类,并调用loadBeanDefinitionsForConfigurationClass()
![](https://i-blog.csdnimg.cn/blog_migrate/57b25cf708685cc2c54f40a721b645f6.png)
看到这里就很清晰了,这个方法就是把ConfigurationClass类里面的属性值分类好,然后去“对号入座”,看是拥有什么注解,并注册beanDefinition,我们进入其中一个registerBeanDefinitionForImportedConfigurationClass()方法看看
![](https://i-blog.csdnimg.cn/blog_migrate/2ac02519cd41d61f37621d1faa08855a.png)
进入registerBeanDefinitionForImportedConfigurationClass()
![](https://i-blog.csdnimg.cn/blog_migrate/b88274b10cbfcd6022ddd4c2d77b57bf.png)
进入DefaultListableBeanFactory
![](https://i-blog.csdnimg.cn/blog_migrate/1a1e980b5793376777a7d0d68e7ce5b4.png)
最后:作为图灵学院一份子,学习来源于周瑜和徐庶两位我特别敬重的老师,站在巨人的肩膀上增加了自己的理解,本文不以商业化为目的,纯个人学习使用
到这里就结束了!!!