文章目錄
- 1. 概述
- 2. 初識@Imprt注解
- 3. @Import注解的使用
- 3.1 普通類注入Spring容器的方式
- 3.2 實作了ImportSelector介面的類注入Spring容器的方式
- 3.3 實作了ImportBeanDefinitionRegistrar介面的類注入Spring容器的方式
- 4. 原始碼分析
- 4.1 決議@Import注解的時機
- 4.2 3種不同型別的類如何通過@Import注解注入到Spring容器當中原始碼決議
- 5. 后續
1. 概述
@Import 是 Spring 基于 Java 注解配置的主要組成部分,@Import 注解提供了類似 @Bean 注解的功能,本文將介紹@Import注解的使用,并詳細分析該注解的實作原理,同時會學習到Spring當中ImportSelector介面的和ImportBeanDefinitionRegistrar介面的使用和實作原理,
2. 初識@Imprt注解
下圖是Spring當中Import注解的原始碼,可以看出Import可以配合 Configuration, ImportSelector, ImportBeanDefinitionRegistrar 來使用,也可以用于一個普通類的匯入,

@Target表明了它能作用的范圍,可以作用于類、介面、列舉類
屬性僅有一個value,表示的是一個類物件陣列,例如value = {xx.class,yy.class},表示要將xx和yy交給Spring容器管理,
那么這些類又有什么特點呢?
大體可以分成三大類
- 普通類
- 實作了ImportSelector介面的類
- 實作了ImportBeanDefinitionRegistrar介面的類
下面來看看這3種類配合上@Import注解的使用區別
3. @Import注解的使用
3.1 普通類注入Spring容器的方式
首先準備一個普通的物體類
package hdu.gongsenlin;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:26
* @Description:
*/
public class NorMal {
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
測驗代碼,使用基于java配置類的方式加載Spring的應用背景關系,
AppConfig如下,配置類比較簡單,只是提供了掃描包的路徑
import org.springframework.context.annotation.ComponentScan;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:25
*/
@ComponentScan("hdu.gongsenlin")
public class AppConfig {
}
重頭戲來了,Import標簽作用在下面這個類上
package hdu.gongsenlin;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 18:15
* @Description:
*/
@Configuration
@Import(NorMal.class)
public class MyConfigure {
}
測驗代碼如下
import hdu.gongsenlin.NorMal;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:28
* @Description:
*/
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ac.start();
NorMal normal = ac.getBean(NorMal.class);
System.out.println(normal != null);
}
}
運行的結果是輸出true,表示Normal物件可以通過Spring容器獲取,也就證明了Import注解起作用了,
3.2 實作了ImportSelector介面的類注入Spring容器的方式
先來看看ImportSelector介面的定義,其中有兩個方法
-
String[] selectImports(AnnotationMetadata importingClassMetadata)
回傳一個包含了類全限定名的陣列,這些類會注入到Spring容器當中
-
Predicate< String > getExclusionFilter()
回傳一個謂詞介面,該方法制定了一個對類全限定名的排除規則來過濾一些候選的匯入類,默認不排除過濾,該介面可以不實作,
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* Return a predicate for excluding classes from the import candidates, to be
* transitively applied to all classes found through this selector's imports.
* <p>If this predicate returns {@code true} for a given fully-qualified
* class name, said class will not be considered as an imported configuration
* class, bypassing class file loading as well as metadata introspection.
* @return the filter predicate for fully-qualified candidate class names
* of transitively imported configuration classes, or {@code null} if none
* @since 5.2.4
*/
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
下面來撰寫自己的ImportSelector介面的實作類
package hdu.gongsenlin;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.util.function.Predicate;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-27 19:17
* @Description:
*/
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"hdu.gongsenlin.Normal"};
}
}
修改上面的MyConfigure配置類,僅僅將Import注解里的屬性改一下
@Configuration
@Import(MyImportSelector.class)
public class MyConfigure {
}
測驗代碼如下,沒發生變化,
import hdu.gongsenlin.NorMal;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:28
* @Description:
*/
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ac.start();
NorMal normal = ac.getBean(NorMal.class);
System.out.println(normal != null);
}
}
控制臺的輸出結果:
如果實作了ImportSelector介面的getExclusionFilter()方法,那么執行的結果會有什么變化呢?
MyImportSelector改為如下,表示過濾掉類全限定名為"hdu.gongsenlin.Normal"的類,該類將不會被注入到Spring容器當中,當然這里可以使用通配符的匹配方式,讀者可以自己去嘗試一下,
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"hdu.gongsenlin.Normal"};
}
@Override
public Predicate<String> getExclusionFilter() {
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
if (s.matches("hdu.gongsenlin.Normal")) {
return true;
}
return false;
}
};
return predicate;
}
}
此時再次執行測驗代碼,控制臺輸出如下:

找不到這個類了,說明該類被過濾掉了,沒有被注入到Spring容器當中,
3.3 實作了ImportBeanDefinitionRegistrar介面的類注入Spring容器的方式
該介面的目的是實作類的動態注入,同樣的,先來看看ImportBeanDefinitionRegistrar介面的定義,
一共定義了兩個同名方法,都是用于將類的BeanDefinition注入,
唯一的區別就是,2個引數的方法,只能手動的輸入beanName,而3個引數的方法,可以利用BeanNameGenerator根據beanDefinition自動生成beanName
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* <p>The default implementation delegates to
* {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
* @param importBeanNameGenerator the bean name generator strategy for imported beans:
* {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a
* user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator}
* has been set. In the latter case, the passed-in strategy will be the same used for
* component scanning in the containing application context (otherwise, the default
* component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}).
* @since 5.2
* @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
* @see ConfigurationClassPostProcessor#setBeanNameGenerator
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* <p>The default implementation is empty.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
下面來撰寫ImportBeanDefinitionRegistrar的實作類
package hdu.gongsenlin;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:34
* @Description:
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(NorMal.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
registry.registerBeanDefinition("normal", beanDefinition);
}
//或者 使用如下的方法也可以,自動生成beanName
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(NorMal.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
String beanName = importBeanNameGenerator.generateBeanName(beanDefinition, registry);
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
測驗代碼不變,執行結果為true,這里就不貼圖了,
4. 原始碼分析
通過上面的例子,@Import注解的三種使用方式相信讀者已經掌握了,接下來就到了分析原始碼,看看Spring中是何時決議@Import注解,又是如何將3種型別的類注入到Spring容器當中的,
4.1 決議@Import注解的時機
在之前的一篇博客當中,詳細的介紹過invokeBeanFactoryPostProcessors方法的執行邏輯,該方法是Spring容器啟動的時候,會觸發的一個函式,不清楚的讀者不妨看看這篇博客《Spring IOC—invokeBeanFactoryPostProcessors》,在這里Spring當中內置的掃描器會執行它的掃描處理邏輯,而對于@Import的決議就發生在其中,下面來看看原始碼:
代碼定位到ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

呼叫processConfigBeanDefinitions,看名字就知道是處理配置類,也就是加了@Configuration的類,除此之外,Appconfig也屬于配置類,
processConfigBeanDefinitions代碼如下:

這一部分是遍歷當前容器中已經存在的BeanDefinition,將配置類加入到configCandidates集合當中,如果該集合為空,會直接回傳,本文中測驗代碼的Debug的結果顯示,此處有一個配置類appConfig,疑問來了不是還有一個MyConfigure配置類嗎?因為此時Spring內置的掃描器還沒有執行作業,所以此時容器中還不存在MyConfigure這個配置類,此時只有appConfig一個配置類,

接著繼續看下面的代碼,根據@Order注解排序,一系列的檢測,不重要,暫時跳過,

接下來的代碼就比較重要了,初始化一個ConfigurationClassParser,呼叫parse方法用于決議配置類,

parse方法如下,遍歷配置類候選集合,此時只有一個appConfig,它屬于AnnotatedBeanDefinition型別,通過第一個if,執行parse(AnnotationMetadata metadata, String beanName),

parse(AnnotationMetadata metadata, String beanName)代碼如下:

將元資料資訊和beanName封裝成ConfigurationClass物件,呼叫processConfigurationClass方法:

具體的處理邏輯由doProcessConfigurationClass實作,紅色框框內執行掃描,此時只有Spring內置的掃描器,掃描結束后,會得到掃描后的BeanDefinitionHolder集合,

遍歷BeanDefinitionHolder集合,如果存在加了注解@Configuration的類,則會遞回的呼叫parse,回到剛才分析過的parse接著往下執行,

之后還是會進入到doProcessConfigurationClass方法,此時決議的是MyConfigure,終于到了本節要找的地方了,@Import注解就是在這里執行的,

總結:@Import注解執行的時機,決議配置類的時候,由ConfigurationClassParser當中的processImports來處理,
4.2 3種不同型別的類如何通過@Import注解注入到Spring容器當中原始碼決議
接著上面的分析,看看processImports是如何實作的
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {//準備注入的候選類集合為空 直接回傳
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {//回圈注入的檢查
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {//遍歷注入的候選集合
/**
* 如果是實作了ImportSelector介面的類
*/
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
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) {//todo 延遲注入 這里還沒有研究 有空了看看
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {//呼叫selector當中的selectImports方法,得到要注入的類的全限定名
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
// 獲得元類資訊
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// 遞回的處理注入的類
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
/**
* 如果是ImportBeanDefinitionRegistrar 則configClass.addImportBeanDefinitionRegistrar 提前放到一個map當中
*/
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);//實體化
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());//放到一個map中
}
/**
* 如果是普通類
*/
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
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();
}
}
}
- 判斷注入的候選集合是否為空,空的話直接回傳
- 回圈注入的檢查
- 遍歷注入的候選集合
- 3種型別的lei執行不同的邏輯
- 實作了ImportSelector介面的類,呼叫getExclusionFilter()方法,如果不為空,那么就進行過濾,過濾后呼叫selectImports方法(),得到要注入的類的全限定名,根據類全限定名,得到類元資訊,然后遞回的呼叫processImports方法
- 實作了ImportBeanDefinitionRegistrar介面的類,會實體化這個類,放入集合importBeanDefinitionRegistrars當中,
- 普通型別的類(上面兩個都不滿足),那么就把它當作是配置類來處理,呼叫processConfigurationClass方法,最侄訓放入到configurationClasses這個集合當中,
- 經過一系列的遞回呼叫,實作了ImportBeanDefinitionRegistrar介面的類,會放入到importBeanDefinitionRegistrars集合當中,其余的類都放入到configurationClasses集合當中,
之后就會回到processConfigBeanDefinitions方法,也就是執行完了ConfigurationClassParser的parse方法
此時會執行loadBeanDefinitions將configurationClasses集合當中類加載的Spring容器當中,并且從 importBeanDefinitionRegistrars 快取當中拿到所有的ImportBeanDefinitionRegistrar并執行registerBeanDefinitions方法,

5. 后續
本文遺留了兩個問題
- DeferredImportSelector,延遲注入是什么意思,如何實作的?
- 回圈注入是如何產生的又是如何避免的?
這兩個問題我也還不清楚,讀者可以去研究一下,私下一起討論,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/254062.html
標籤:java
上一篇:Java并發編程-常用的輔助類
