
微信搜索:碼農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之前被呼叫:
EnvironmentAwareBeanFactoryAwareBeanClassLoaderAwareResourceLoaderAware
另外,該介面實作類可以提供一個或多個具有以下形參型別的建構式:
EnvironmentBeanFactoryClassLoaderResourceLoader
如果你想要推遲匯入配置類,直到處理完所有的@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,若對你有幫助,歡迎給個?,感謝~~🌹🌹🌹
https://github.com/gozhuyinglong/blog-demos/tree/main/spring-source-analysis/src/main/java/io/github/gozhuyinglong/importanalysis
推薦閱讀
- Java反射機制:跟著代碼學反射
- JDK動態代理:不僅要學會用,更要掌握其原理
關于作者
| 專案 | 內容 |
|---|---|
| 公眾號 | 碼農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 |

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/267071.html
標籤:java
