微信搜索:码农StayUp
主页地址:https://gozhuyinglong.github.io
源码分享:https://github.com/gozhuyinglong/blog-demos
平时喜欢看源码的小伙伴,应该知道Spring中大量使用了@Import
注解。该注解是Spring用来导入配置类的,等价于Spring XML
中的<import/>
元素。
本文将对该注解进行介绍,并通过实例演示它导入配置类的四种方式,最后对该注解进行源码解析。
话不多说,走起~
简介
@Import
注解的全类名是org.springframework.context.annotation.Import
。其只有一个默认的value
属性,该属性类型为Class<?>[]
,表示可以传入一个或多个Class
对象。
通过注释可以看出,该注解有如下作用:
- 可以导入一个或多个组件类(通常是
@Configuration
配置类) - 该注解的功能与
Spring XML
中的<import/>
元素相同。可以导入@Configuration
配置类、ImportSelect
和ImportBeanDefinitionRegistrar
的实现类。从4.2版本开始,还可以引用常规组件类(普通类),该功能类似于AnnotationConfigApplicationContext.register
方法。 - 该注解可以在类中声明,也可以在元注解中声明。
- 如果需要导入
XML
或其他非@Configuration
定义的资源,可以使用@ImportResource
注释。
导入配置类的四种方式
源码注释写得很清楚,该注解有四种导入方式:
- 普通类
-
@Configuration
配置类 -
ImportSelector
的实现类 -
ImportBeanDefinitionRegistrar
的实现类
下面我们逐个来介绍~
准备工作
创建四个配置类:ConfigA、ConfigB、ConfigC、ConfigD。其中ConfigB中增加@Configuration
注解,表示为配置类,其余三个均为普通类。
ConfigA:
public class ConfigA {
public void print() {
System.out.println("输出:ConfigA.class");
}
}
ConfigB:
@Configuration
public class ConfigB {
public void print() {
System.out.println("输出:ConfigB.class");
}
}
ConfigC:
public class ConfigC {
public void print() {
System.out.println("输出:ConfigC.class");
}
}
ConfigD:
public class ConfigD {
public void print() {
System.out.println("输出:ConfigD.class");
}
}
再创建一个主配置类Config,并试图通过@Resource
注解将上面四个配置类进行注入。当然,这样是不成功的,还需要将它们进行导入。
@Configuration
public class Config {
@Resource
ConfigA configA;
@Resource
ConfigB configB;
@Resource
ConfigC configC;
@Resource
ConfigD configD;
public void print() {
configA.print();
configB.print();
configC.print();
configD.print();
}
}
方式一:导入普通类
导入普通类非常简单,只需在@Import
传入类的Class
对象即可。
@Configuration
@Import(ConfigA.class)
public class Config {
...
}
方式二:导入@Configuration
配置类
导入配置类与导入普通类一样,在@Import
注解中传入目标类的Class
对象。
@Configuration
@Import({ConfigA.class,
ConfigB.class})
public class Config {
...
}
方式三:导入ImportSelector
的实现类
ImportSelector
接口的全类名为org.springframework.context.annotationImportSelector
。其主要作用的是收集需要导入的配置类,并根据条件来确定哪些配置类需要被导入。
该接口的实现类同时还可以实现以下任意一个Aware
接口,它们各自的方法将在selectImport
之前被调用:
另外,该接口实现类可以提供一个或多个具有以下形参类型的构造函数:
如果你想要推迟导入配置类,直到处理完所有的@Configuration
。那么你可以使用DeferredImportSelector
下面我们创建一个实现该接口的类 MyImportSelector。
看下面示例:
在selectImports
方法中,入参AnnotationMetadata
为主配置类 Config 的注解元数据。
返回值为目标配置类 ConfigC 的全类名,这里是一个数组,表示可以导入多个配置类。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"io.github.gozhuyinglong.importanalysis.config.ConfigC"};
}
}
在配置类 Config 中导入 MyImportSelector 类。
@Configuration
@Import({ConfigA.class,
ConfigB.class,
MyImportSelector.class})
public class Config {
...
}
方式四:导入ImportBeanDefinitionRegistrar
的实现类
该接口的目的是有选择性的进行注册Bean
,注册时可以指定Bean
名称,并且可以定义bean的级别。其他功能与ImportSelector
类似,这里就不再赘述。
下面来看示例:
创建一个实现 ImportBeanDefinitionRegistrar
接口的类 MyImportBeanDefinitionRegistrar,并在 registerBeanDefinitions
方法中注册 configD 类。
入参 AnnotationMetadata
为主配置类 Config 的注解元数据;BeanDefinitionRegistry
参数可以注册Bean
的定义信息。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("configD", new RootBeanDefinition(ConfigD.class));
}
}
在配置类 Config 中导入 MyImportBeanDefinitionRegistrar 类。
@Configuration
@Import({ConfigA.class,
ConfigB.class,
MyImportSelector.class,
MyImportBeanDefinitionRegistrar.class})
public class Config {
...
}
测试结果
创建一个测试类 ImportDemo,看上面四个配置类是否被注入。
public class ImportDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
Config config = ctx.getBean(Config.class);
config.print();
}
}
输出结果:
输出:ConfigA.class
输出:ConfigB.class
输出:ConfigC.class
输出:ConfigD.class
通过输出结果可以看出,这四个配置类被导入到主配置类中,并成功注入。
源码解析
ConfigurationClassParser
类为Spring的工具类,主要用于分析配置类,并产生一组ConfigurationClass
对象(因为一个配置类中可能会通过@Import
注解来导入其它配置类)。也就是说,其会递归的处理所有配置类。
doProcessConfigurationClass
其中的doProcessConfigurationClass
方法是处理所有配置类的过程,其按下面步骤来处理:
- @Component注解
- @PropertySource注解
- @ComponentScan注解
- @Import注解
- @ImportResource注解
- @Bean注解
- 配置类的接口上的默认方法
- 配置类的超类
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 1.首先会递归的处理所有成员类,即@Component注解
processMemberClasses(configClass, sourceClass, filter);
}
// 2.处理所有@PropertySource注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 3.处理所有@ComponentScan注解
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// 配置类的注解为@ComponentScan-> 立即执行扫描
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 检查扫描过的BeanDefinition集合,看看是否有其他配置类,如果需要,递归解析
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// 4.处理所有@Import注解
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// 5.处理所有@ImportResource注解
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// 6.处理标注为@Bean注解的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 7.处理配置类的接口上的默认方法
processInterfaces(configClass, sourceClass);
// 8.处理配置类的超类(如果有的话)
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// 处理完成
return null;
}
processImports
processImports
方法为处理@Import
注解导入的配置类,是我们本篇的主题。
该方法会循环处理每一个由@Import导入的类:
- ImportSelector类的处理
- ImportBeanDefinitionRegistrar类的处理
- 其它类统一按照@Configuration类来处理,所以加不加@Configuration注解都能被导入
/**
* 处理配置类上的@Import注解引入的类
*
* @param configClass 配置类,这里是Config类
* @param currentSourceClass 当前资源类
* @param importCandidates 该配置类中的@Import注解导入的候选类列表
* @param exclusionFilter 排除过滤器
* @param checkForCircularImports 是否循环检查导入
*/
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// 如果该@Import注解导入的列表为空,直接返回
if (importCandidates.isEmpty()) {
return;
}
// 循环检查导入
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// 循环处理每一个由@Import导入的类
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// 1. ImportSelector类的处理
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
// 1.1 若是DeferredImportSelector接口的实现,则延时处理
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 1.2 在这里调用我们的ImportSelector实现类的selectImports方法
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// 1.3 递归处理每一个selectImports方法返回的配置类
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 2. ImportBeanDefinitionRegistrar类的处理
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// 3. 其它类统一按照@Configuration类来处理,所以加不加@Configuration注解都能被导入
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
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();
}
}
}
总结
通过上面源码的解析可以看出,@Import
注解主要作用是导入外部类的,并且普通类也会按照@Configuration
类来处理。这大大方便了我们将自己的组件类注入到容器中了(无需修改自己的组件类)。
源码分享
完整代码请访问我的Github,若对你有帮助,欢迎给个⭐,感谢~~🌹🌹🌹
推荐阅读
关于作者
项目 | 内容 |
---|---|
公众号 | 码农StayUp(ID:AcmenStayUp) |
主页 | https://gozhuyinglong.github.io |
CSDN | https://blog.csdn.net/gozhuyinglong |
掘进 | https://juejin.cn/user/1239904849494856 |
Github | https://github.com/gozhuyinglong |
Gitee | https://gitee.com/gozhuyinglong |