@Import注解是用来整合所有@Configuration注解中定义的bean配置。
这其实很像我们将多个xml配置文件导入到单个文件的情形。@Import注解实现了相同的功能。
为什么要拆分XML配置文件呢?原因很简单,为了对配置文件进行分类,避免配置文件过大,这是一种管理的思想。
一,Import注解源码
public @interface Import {
Class<?>[] value();
}
从源码我们可以看到,Import注解只有一个属性,就是这个value,value的类型是一个Class数组,也就是说,Import注解可以配置多个Class配置类。
二,Import注解的功能
Import注解等价于XML配置文件中的import标签。可以在主配置文件中导入其他配置文件。Import注解实现的功能其实也是类似的,在主配置类中导入其他配置类,这样的设计符合单一职责的原则,方便扩展和重组。
在Spring4.2之前,Import注解只支持导入配置类,什么是配置类呢?其实就是标有@Configuration注解的类。
从Spring4.2开始,Import注解支持导入普通的类。
三,Import注解的实现原理
Import注解概括来说,支持三种类型的类的注入。
1,普通类。
2,实现ImportSelector接口的类。
3,实现ImportSelectorDefinitionRegistrar接口的类。
这个逻辑在ConfigurationClassParse. processImport()中。感兴趣的朋友可以自行查看。
OK,接下来我们看一下具体实现,关键代码都在这个后置处理器:
ConfigurationClassPostProcessor
第一步:解析得到beanName
读取所有标有@Import注解的类,这里使用的读取器是ConfigurationClassBeanDefinitionReader。
这里使用的beaName生成器是BeanNameGenerator或者AnnotationBeanNameGenerator。
String[] candidateNames = registry. getBeanDefinitionNames();
最终,ConfigurationClassPostProcessor调用BeanDefinitionRegistry的getBeanDefinitionNames()方法,该方法返回一个字符串数组,数组中存放的就是beanName。
注意:这里获得的candidateNames数组,是标注有@Configuration注解的java类。
第二步:解析处理获得beanDefinition
BeanDefinition beanDef = registry. getBeanDefinition(beanName);
这里的registry对象和上面一样,都是BeanDefinitionRegistry类的实例。
BeanDefinitionRegistry是一个接口,这个接口定义了7个方法,如下:
// 注册bean
void registerBeanDefinition(String beanName, BeanDefinition beanDef);
// 移出bean
void removeBeanDefinition(String beanName);
// 获取BeanDefinition
BeanDefinition getBeanDefinition(String beanName);
// 判断是否存在某个bean
boolean containsBeanDefinition(String beanName);
// 获取beanName数组
String[] getBeanDefinitionNames();
// 统计所有的bean
int getBeanDefinitionCount();
// 判断bean是否在使用
boolean isBeanInUse(String beanName);
第三步:根据注解类型进行分类处理。
首先来看注解分类,BeanDefinition的属性CONFIGURATION_CLASS_ATTRIBUTE,该属性有3个可选值:
full:标注@Configuration注解的类
lite:标注candidateIndicators类型注解的类。candidateIndicators可选值有4个:
Component.class.getName()
ComponentScan.class.getName()
Import.class.getName()
ImportResource.class.getName()
其实对应的就是下面4个注解:
@Component
@ComponentScan
@Import
@ImportResource
空:其他注解。
OK,搞清楚注解的分类以后,我们接下来继续分析。
首先,把标注@Configuration注解的类存入configCandidate这个容器。
接下来,第四步处理的就是这个容器中的bean,也就是标注@Configuration注解的类。
第四步:ConfigurationClassParser
Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidate);
parser. parse(candidates);
这行代码的主要功能就是解析标有@Configuration注解的类。
解析的流程是什么样的呢?解析操作主要做了什么?
真正处理@Configuration注解的方法是ConfigurationClassParser.processConfigurationClass()。这里首先处理@Configuration注解,然后会迭代处理@Import注解导入的其他配置类。
主要处理符合以下条件的配置类:
成员内部类。
核心方法:processMemberClasses。
@ComponentScan注解指定的包下的配置类。
关键类:ComponentScanAnnotationParser。
@Import直接指定的配置类。
核心方法:this.processImports。
@ImportResource注解指定的XML文件中的配置类。
核心方法:attributesForRepeatable。
@Bean注解的方法。
核心方法:retrieveBeanMethodMetaData。
@Bean注解的接口默认方法。
核心方法:processInterfaces。
parser. validate();
这行代码的功能是校验。校验什么呢?主要校验2点:
第一点:校验类是否是final类型的。
@Configuration class may not be final
也就是说标注@Configuration注解的类不能被final修饰。
第二点:校验类中的方法是否可以重写。
@Bean method must not be private or final
也就是说标注@Configuration注解的类中的标注@Bean注解的方法,不能被private或final修饰。
为什么要做这个校验呢?因为标注有@Configuration注解的类后续会通过动态代理的方式生成子类。
第五步,把解析得到的beanName和beanDefinition信息经过包装,然后调用DefaultListableBeanFactory的registerSingleton()方法,注册bean到IOC容器。
这里使用的包装器是BeanDefinitionHolder。经过BeanDefinitionHolder的处理以后,bean的定义信息结构如下:
beanName
BeanDefinition对象
aliases字符串数组
四,总结
主要流程如下:
![](https://img.haomeiwen.com/i16004747/b2f185135e6ffc88.jpg)
关键组件
后置处理器:ConfigurationClassBeanProcessor
@Configuration注解处理类:ConfigurationClassParser
读取器:ConfigurationClassBeanDefinitionReader
beanDefunition处理器:BeanDefinitionRegistry
beaName生成器:BeanNameGenerator或者AnnotationBeanNameGenerator。
beanDefinition包装器:BeanDefinitionHolder
bean工厂:DefaultListableBeanFactory
网友评论