Spring注解@Import原理解析
正文
在項目開發(fā)的過程中,我們會遇到很多名字為 @Enablexxx 的注解,比如@EnableApolloConfig、 @EnableFeignClients、 @EnableAsync 等。他們的功能都是通過這樣的注解實現(xiàn)一個開關,決定了是否開啟某個功能模塊的所有組件的自動化配置,這極大的降低了我們的使用成本。
那么你是好奇過 @Enablexxx 是如何達到這種效果呢,其作用機制是怎么樣的呢?
@Import 原理
按照默認的習慣,我們會把某個功能模塊的開啟注解定義為 @Enablexxx,功能的實現(xiàn)和名字格式其實無關,而是其內部實現(xiàn),這里用 @EnableAsync 來舉例子。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
……
}
可以看到除了3個通用注解,還有一個@Import(AsyncConfigurationSelector.class)注解,顯然它真正在這里發(fā)揮了關鍵作用,它可以往容器中注入一個配置類。
在 Spring 容器啟動的過程中,執(zhí)行到調用invokeBeanFactoryPostProcessors(beanFactory)方法的時候,會調用所有已經(jīng)注冊的 BeanFactoryPostProcessor,然后會調用實現(xiàn) BeanDefinitionRegistryPostProcessor 接口的后置處理器 ConfigurationClassPostProcessor ,調用其 postProcessBeanDefinitionRegistry() 方法, 在這里會解析通過注解配置的類,然后調用 ConfigurationClassParser#doProcessConfigurationClass() 方法,最終會走到processImports()方法,對 @Import 注解進行處理,具體流程如下。
如果這部分流程不是很理解,推薦詳細閱讀一下 Spring 生命周期相關的代碼,不過不重要,不影響理解后面的內容。

@Import 注解的功能是在ConfigurationClassParser類的 processImports()方法中實現(xiàn)的,對于這個方法我已經(jīng)做了詳細的注釋,請查看。
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 {
// 添加到棧中,用于處理循環(huán)import的問題
this.importStack.push(configClass);
try {
// 遍歷每一個@Import注解的類
for (SourceClass candidate : importCandidates) {
// 1.
// 檢驗配置類Import引入的類是否是ImportSelector子類
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
// 候選類是一個導入選擇器->委托來確定是否進行導入
Class<?> candidateClass = candidate.loadClass();
// 通過反射生成一個ImportSelect對象
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);
}
// 判斷引用選擇器是否是DeferredImportSelector接口的實例
// 如果是則應用選擇器將會在所有的配置類都加載完畢后加載
if (selector instanceof DeferredImportSelector) {
// 將選擇器添加到deferredImportSelectorHandler實例中,預留到所有的配置類加載完成后統(tǒng)一處理自動化配置類
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 獲取引入的類,然后使用遞歸方式將這些類中同樣添加了@Import注解引用的類
// 執(zhí)行 ImportSelector.selectImports
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// 遞歸處理,被Import進來的類也有可能@Import注解
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
// 2.
// 如果是實現(xiàn)了ImportBeanDefinitionRegistrar接口的bd
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
// 候選類是ImportBeanDefinitionRegistrar -> 委托給當前注冊器注冊其他bean
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
/**
* 放到當前configClass的importBeanDefinitionRegistrars中
* 在ConfigurationClassPostProcessor處理configClass時會隨之一起處理
*/
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
// 候選類既不是ImportSelector也不是ImportBeanDefinitionRegistrar-->將其作為@Configuration配置類處理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
/**
* 如果Import的類型是普通類,則將其當作帶有@Configuration的類一樣處理
*/
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
……
finally {
this.importStack.pop();
}
}
}
上述代碼的核心邏輯無非就是如下幾個步驟。
- 找到被 @Import 修飾的候選類集合,依次循環(huán)遍歷。
- 如果該類實現(xiàn)了
ImportSelector接口,就調用ImportSelector的selectImports()方法,這個方法返回的是一批配置類的全限定名,然后遞歸調用processImports()繼續(xù)解析這些配置類,比如可以 @Import 的類里面有 @Import 注解,在這里可以遞歸處理。 - 如果被修飾的類沒有實現(xiàn)
ImportSelector接口,而是實現(xiàn)了ImportBeanDefinitionRegistrar接口,則把對應的實例放入importBeanDefinitionRegistrars這個Map中,等到ConfigurationClassPostProcessor處理 configClass 的時候,會與其他配置類一同被調用ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法,以實現(xiàn)往 Spring 容器中注入一些 BeanDefinition。 - 如果以上的兩個接口都未實現(xiàn),則進入 else 邏輯,將其作為普通的 @Configuration 配置類進行解析。
所以到這里,你應該明白 @Import 的作用機制了吧。對上述邏輯我總結了一張圖,如下。

示例 @EnableAsync
繼續(xù)之前提到的 @EnableAsync 作為例子,源碼如下。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
//
@Override
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
……
// 獲取 Mode
AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
// 模板方法,由子類去實現(xiàn)
String[] imports = selectImports(adviceMode);
if (imports == null) {
throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
}
return imports;
}
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
@Override
@Nullable
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
}
它通過 @Import 注解引入了AsyncConfigurationSelector配置類,它繼承了 AdviceModeImportSelector 類,而后者實現(xiàn)了 ImportSelector 接口,里面的實現(xiàn)了一個由注解指定 mode 屬性來決定返回的配置類的邏輯,而 mode 的默認值就是 AdviceMode.PROXY。

對應 switch 邏輯,將返回 ProxyAsyncConfiguration類的全限定名。這就對應了 @Import 處理邏輯的第一個 if 邏輯塊,它將會解析這個類,然后遞歸調用processImports(),再次進入此方法,進入第三個else邏輯塊,將其當作一個普通配置類解析??梢钥吹?ProxyAsyncConfiguration 其實就是 @Configuration 類,它的作用是注冊一個 Bean 對象 AsyncAnnotationBeanPostProcessor。
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
……
return bpp;
}
}以上就是Spring注解@Import原理解析的詳細內容,更多關于Spring注解@Import原理的資料請關注腳本之家其它相關文章!
相關文章
Java的線程池ThreadPoolExecutor及多種線程池實現(xiàn)詳解
這篇文章主要介紹了Java的線程池ThreadPoolExecutor及多種線程池實現(xiàn)詳解,ThreadPoolExecutor 使用 int 的高 3 位來表示線程池狀態(tài),低 29 位表示線程數(shù)量,之所以將信息存儲在一個變量中,是為了保證原子性,需要的朋友可以參考下2024-01-01
如何用Java將數(shù)據(jù)庫的數(shù)據(jù)生成pdf返回給前端用戶下載
本文詳細介紹了使用SpringBoot、iText庫、MyBatis等技術從數(shù)據(jù)庫中選取數(shù)據(jù)并生成PDF文件的后端處理流程,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-09-09
SpringBoot實現(xiàn)動態(tài)數(shù)據(jù)源切換的項目實踐
在實際開發(fā)過程中,我們經(jīng)常遇到需要同時操作多個數(shù)據(jù)源的情況,本文主要介紹了SpringBoot實現(xiàn)動態(tài)數(shù)據(jù)源切換的項目實踐,具有一定的參考價值,感興趣的可以了解一下2024-04-04
SpringBoot使用redis生成訂單號的實現(xiàn)示例
在電商系統(tǒng)中,生成唯一訂單號是常見需求,本文介紹如何利用SpringBoot和Redis實現(xiàn)訂單號的生成,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-09-09
spring中在xml配置中加載properties文件的步驟
這篇文章主要介紹了在spring中如何在xml配置中加載properties文件,本文分步驟給大家介紹在XML配置中加載properties文件的方法,需要的朋友可以參考下2023-07-07
基于SpringBoot?使用?Flink?收發(fā)Kafka消息的示例詳解
這篇文章主要介紹了基于SpringBoot?使用?Flink?收發(fā)Kafka消息,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01

