微信搜索:碼農(nóng)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
的實現(xiàn)類忆蚀。從4.2版本開始,還可以引用常規(guī)組件類(普通類)姑裂,該功能類似于AnnotationConfigApplicationContext.register
方法馋袜。 - 該注解可以在類中聲明,也可以在元注解中聲明舶斧。
- 如果需要導入
XML
或其他非@Configuration
定義的資源欣鳖,可以使用@ImportResource
注釋。
導入配置類的四種方式
源碼注釋寫得很清楚捧毛,該注解有四種導入方式:
- 普通類
-
@Configuration
配置類 -
ImportSelector
的實現(xiàn)類 -
ImportBeanDefinitionRegistrar
的實現(xiàn)類
下面我們逐個來介紹~
準備工作
創(chuàng)建四個配置類: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");
}
}
再創(chuàng)建一個主配置類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
的實現(xiàn)類
ImportSelector
接口的全類名為org.springframework.context.annotationImportSelector
茶没。其主要作用的是收集需要導入的配置類,并根據(jù)條件來確定哪些配置類需要被導入晚碾。
該接口的實現(xiàn)類同時還可以實現(xiàn)以下任意一個Aware
接口抓半,它們各自的方法將在selectImport
之前被調(diào)用:
另外,該接口實現(xiàn)類可以提供一個或多個具有以下形參類型的構造函數(shù):
如果你想要推遲導入配置類格嘁,直到處理完所有的@Configuration
笛求。那么你可以使用DeferredImportSelector
下面我們創(chuàng)建一個實現(xiàn)該接口的類 MyImportSelector。
看下面示例:
在selectImports
方法中糕簿,入?yún)?code>AnnotationMetadata為主配置類 Config 的注解元數(shù)據(jù)探入。
返回值為目標配置類 ConfigC 的全類名,這里是一個數(shù)組懂诗,表示可以導入多個配置類新症。
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
的實現(xiàn)類
該接口的目的是有選擇性的進行注冊Bean
响禽,注冊時可以指定Bean
名稱,并且可以定義bean的級別荚醒。其他功能與ImportSelector
類似芋类,這里就不再贅述。
下面來看示例:
創(chuàng)建一個實現(xiàn) ImportBeanDefinitionRegistrar
接口的類 MyImportBeanDefinitionRegistrar界阁,并在 registerBeanDefinitions
方法中注冊 configD 類侯繁。
入?yún)?AnnotationMetadata
為主配置類 Config 的注解元數(shù)據(jù);BeanDefinitionRegistry
參數(shù)可以注冊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 {
...
}
測試結果
創(chuàng)建一個測試類 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的工具類惰拱,主要用于分析配置類,并產(chǎn)生一組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-> 立即執(zhí)行掃描
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
注解導入的配置類婆排,是我們本篇的主題。
該方法會循環(huán)處理每一個由@Import導入的類:
- ImportSelector類的處理
- ImportBeanDefinitionRegistrar類的處理
- 其它類統(tǒng)一按照@Configuration類來處理控硼,所以加不加@Configuration注解都能被導入
/**
* 處理配置類上的@Import注解引入的類
*
* @param configClass 配置類泽论,這里是Config類
* @param currentSourceClass 當前資源類
* @param importCandidates 該配置類中的@Import注解導入的候選類列表
* @param exclusionFilter 排除過濾器
* @param checkForCircularImports 是否循環(huán)檢查導入
*/
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// 如果該@Import注解導入的列表為空,直接返回
if (importCandidates.isEmpty()) {
return;
}
// 循環(huán)檢查導入
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// 循環(huán)處理每一個由@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接口的實現(xiàn)卡乾,則延時處理
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 1.2 在這里調(diào)用我們的ImportSelector實現(xiàn)類的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. 其它類統(tǒng)一按照@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误堡,若對你有幫助古话,歡迎給個?,感謝~~??????
推薦閱讀
關于作者
項目 | 內(nèi)容 |
---|---|
公眾號 | 碼農(nóng)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 |