@configuration是怎么处理的
@import是怎么处理的
Import什么时候放到容器
1、通过Ctrl+Shift+R进行全局查找
查找到后就打断点
2、打完断点后,debug启动
3、下面就是调用栈
4、流程走下来发现这里获取全限定类名
5、通过调用栈取出其调用节点
AbstractApplicationContext#refresh
AbstractApplicationContext#invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
ConfigurationClassPostProcessor#processConfigBeanDefinitions
进到ConfigurationClassUtils类调用静态方法将Component、ComponentScan、Import、ImportResource
的全限定类名加入candidateIndicators集合中
这里得出:Import类是在ConfigurationClassUtils静态代码块时候放进去的
6、继续下一步
7、通过configurationClass为参数获取元数据
8、
9、
10、
11、(这里和第5点的结论一致)
12、如果有@Order注解则进行排序
13、将参数通过parse方法进行解析
14、继续进入解析
注意: 这个解析里面含有两个获取信息,也就是先去到两个方法获取两个参数后,才能执行外面的parse()方法
15、第一次获取是元数据
16、继续进入到getBeanName方法
17、通过getBeanName方法获取配类的名字
18、进到真的的parse方法进行解析
19、继续进入
(按上面14的类似,这里也要先调用ConfigurationClass创建一个对象作为参数,再进行调用的)
20、
21、
22、
22、
23、
24、
由上面的debug得出,很多解析都是在ConfigurationClassPostProcessor类中
25、具体的流程截图
在关键部位打上断点,然后debug模式启动,观察其调用栈信息
debug模式
1、
===============
调用栈如下,有空再画时序图
AbstractApplicationContext#refreshAbstractApplicationContext#invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
ConfigurationClassPostProcessor#processConfigBeanDefinitions
ConfigurationClassParser#parse
ConfigurationClassParser#parse
ConfigurationClassParser#processConfigurationClass
ConfigurationClassParser#doProcessConfigurationClass(对标有相关注解的文件进行解析)
===发现网上这篇写得也不错,就考下来放到下面,以便学习加深理解=======
文章目录
- 概述
- 初识@Imprt注解
- @Import注解的使用
3.1 普通类注入Spring容器的方式
3.2 实现了ImportSelector接口的类注入Spring容器的方式
3.3 实现了ImportBeanDefinitionRegistrar接口的类注入Spring容器的方式 - 源码分析
4.1 解析@Import注解的时机
4.2 3种不同类型的类如何通过@Import注解注入到Spring容器当中源码解析 - 后续
概述
@Import 是 Spring 基于 Java 注解配置的主要组成部分,@Import 注解提供了类似 @Bean 注解的功能。本文将介绍@Import注解的使用,并详细分析该注解的实现原理,同时会学习到Spring当中ImportSelector接口的和ImportBeanDefinitionRegistrar接口的使用和实现原理。
初识@Imprt注解
下图是Spring当中Import注解的源码,可以看出Import可以配合 Configuration, ImportSelector, ImportBeanDefinitionRegistrar 来使用,也可以用于一个普通类的导入。
那么这些类又有什么特点呢?
大体可以分成三大类
1、普通类
2、实现了ImportSelector接口的类
3、实现了ImportBeanDefinitionRegistrar接口的类
下面来看看这3种类配合上@Import注解的使用区别
@Import注解的使用
1、普通类注入Spring容器的方式
1、首先准备一个普通的实体类
package edu.test;
public class NorMal {
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
测试代码,使用基于java配置类的方式加载Spring的应用上下文。
2、AppConfig如下,配置类比较简单,只是提供了扫描包的路径
@ComponentScan("edu.test")
public class AppConfig {
}
3、重头戏来了,Import标签作用在下面这个类上
package edu.test;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(NorMal.class) // 引入NorMal类,并交给Spring容器管理
public class MyConfigure {
}
4、测试代码如下
import edu.test.NorMal;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ac.start();
NorMal normal = ac.getBean(NorMal.class);
System.out.println(normal != null);
}
}
运行的结果是输出true,表示Normal对象可以通过Spring容器获取,也就证明了Import注解起作用了。
2、实现了ImportSelector接口的类注入Spring容器的方式
1、先来看看ImportSelector接口的定义,其中有两个方法
String[] selectImports(AnnotationMetadata importingClassMetadata)
返回一个包含了类全限定名的数组,这些类会注入到Spring容器当中
Predicate< String > getExclusionFilter()
返回一个谓词接口,该方法制定了一个对类全限定名的排除规则来过滤一些候选的导入类,
默认不排除过滤。该接口可以不实现。
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
2、下面来编写自己的ImportSelector接口的实现类
package edu.test;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.util.function.Predicate;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"edu.test.Normal"};
}
}
3、修改上面的MyConfigure配置类,仅仅将Import注解里的属性改一下
@Configuration
@Import(MyImportSelector.class)
public class MyConfigure {
}
4、测试代码如下,没发生变化。
import edu.test.NorMal;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ac.start();
NorMal normal = ac.getBean(NorMal.class);
System.out.println(normal != null);
}
}
5、控制台的输出结果:true
如果实现了ImportSelector接口的getExclusionFilter()方法,那么执行的结果会有什么变化呢?
MyImportSelector改为如下,表示过滤掉类全限定名为”edu.test.Normal”的类,该类将不会被注入到Spring容器当中。当然这里可以使用通配符的匹配方式,读者可以自己去尝试一下。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"hdu.gongsenlin.Normal"};
}
@Override
public Predicate<String> getExclusionFilter() {
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
if (s.matches("hdu.gongsenlin.Normal")) {
return true;
}
return false;
}
};
return predicate;
}
}
6、此时再次执行测试代码,控制台输出如下:
找不到这个类了,说明该类被过滤掉了,没有被注入到Spring容器当中。(这个类在新版本已经没有了)
3、实现了ImportBeanDefinitionRegistrar接口的类注入Spring容器的方式
该接口的目的是实现类的动态注入,同样的,先来看看ImportBeanDefinitionRegistrar接口的定义。
一共定义了两个同名方法,都是用于将类的BeanDefinition注入。
唯一的区别就是,2个参数的方法,只能手动的输入beanName,而3个参数的方法,可以利用BeanNameGenerator根据beanDefinition自动生成beanName(新版本只有两个参数的)
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
1、下面来编写ImportBeanDefinitionRegistrar的实现类
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(NorMal.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
registry.registerBeanDefinition("normal", beanDefinition);
}
//或者 使用如下的方法也可以,自动生成beanName
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(NorMal.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
String beanName = importBeanNameGenerator.generateBeanName(beanDefinition, registry);
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
测试代码不变,执行结果为true,这里就不贴图了。
源码分析
通过上面的例子,@Import注解的三种使用方式相信读者已经掌握了。接下来就到了分析源码,看看Spring中是何时解析@Import注解,又是如何将3种类型的类注入到Spring容器当中的。
1、 解析@Import注解的时机
在之前的一篇博客当中,详细的介绍过invokeBeanFactoryPostProcessors方法的执行逻辑,该方法是Spring容器启动的时候,会触发的一个函数。不清楚的读者不妨看看这篇博客《Spring IOC—invokeBeanFactoryPostProcessors》。在这里Spring当中内置的扫描器会执行它的扫描处理逻辑,而对于@Import的解析就发生在其中。下面来看看源码:
1、
代码定位到ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
调用processConfigBeanDefinitions,看名字就知道是处理配置类,也就是加了@Configuration的类,除此之外,Appconfig也属于配置类。
processConfigBeanDefinitions代码如下:
这一部分是遍历当前容器中已经存在的BeanDefinition,将配置类加入到configCandidates集合当中。如果该集合为空,会直接返回。本文中测试代码的Debug的结果显示,此处有一个配置类appConfig。疑问来了不是还有一个MyConfigure配置类吗?因为此时Spring内置的扫描器还没有执行工作,所以此时容器中还不存在MyConfigure这个配置类,此时只有appConfig一个配置类。
接着继续看下面的代码,根据@Order注解排序,一系列的检测,不重要,暂时跳过。
接下来的代码就比较重要了,初始化一个ConfigurationClassParser,调用parse方法用于解析配置类。
parse方法如下,遍历配置类候选集合,此时只有一个appConfig,它属于AnnotatedBeanDefinition类型,通过第一个if,执行parse(AnnotationMetadata metadata, String beanName)。
parse(AnnotationMetadata metadata, String beanName)代码如下:
将元数据信息和beanName封装成ConfigurationClass对象,调用processConfigurationClass方法:
具体的处理逻辑由doProcessConfigurationClass实现,红色框框内执行扫描,
此时只有Spring内置的扫描器,扫描结束后,会得到扫描后的BeanDefinitionHolder集合。
遍历BeanDefinitionHolder集合,如果存在加了注解@Configuration的类,则会递归的调用parse。回到刚才分析过的parse接着往下执行。
之后还是会进入到doProcessConfigurationClass方法,此时解析的是MyConfigure。终于到了本节要找的地方了,@Import注解就是在这里执行的。
总结:@Import注解执行的时机,解析配置类的时候,由ConfigurationClassParser当中的processImports来处理。
2、种不同类型的类如何通过@Import注解注入到Spring容器当中源码解析
接着上面的分析,看看processImports是如何实现的
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> 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) {
/*如果是实现了ImportSelector接口的类*/
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 (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
/*调用selector当中的selectImports方法,得到要注入的类的全限定名*/
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
/*获得元类信息*/
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
/*候选类是一个重要的定义注册器->委托给它以注册其他bean定义*/
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
/*候选类不是导入选择器或导入定义注册器->将其作为@Configuration类进行处理*/
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
1、判断注入的候选集合是否为空,空的话直接返回
2、循环注入的检查
3、遍历注入的候选集合
A、3种类型的类执行不同的逻辑
B、实现了ImportSelector接口的类,调用getExclusionFilter()方法,如果不为空,那么就进行过滤,过滤后调
用selectImports方法(),得到要注入的类的全限定名。根据类全限定名,得到类元信息。然后递归的调用
processImports方法
C、实现了ImportBeanDefinitionRegistrar接口的类,会实例化这个类,放入集合
importBeanDefinitionRegistrars当中。
D、普通类型的类(上面两个都不满足),那么就把它当作是配置类来处理,调用processConfigurationClass方
法,最终会放入到configurationClasses这个集合当中。
经过一系列的递归调用,实现了ImportBeanDefinitionRegistrar接口的类,会放入到importBeanDefinitionRegistrars集合当中,其余的类都放入到configurationClasses集合当中。
之后就会回到processConfigBeanDefinitions方法,也就是执行完了ConfigurationClassParser的parse方法
此时会执行loadBeanDefinitions将configurationClasses集合当中类加载的Spring容器当中,并且从 importBeanDefinitionRegistrars 缓存当中拿到所有的ImportBeanDefinitionRegistrar并执行registerBeanDefinitions方法。
本文遗留了两个问题
DeferredImportSelector,延迟注入是什么意思,如何实现的?
循环注入是如何产生的又是如何避免的?
原文链接:https://blog.csdn.net/gongsenlin341/article/details/113281596