@Import註解:導入配置類的四種方式&源碼解析

微信搜索:碼農StayUphtml

主頁地址:gozhuyinglong.github.iojava

源碼分享:github.com/gozhuyinglo…git

平時喜歡看源碼的小夥伴,應該知道Spring中大量使用了@Import註解。該註解是Spring用來導入配置類的,等價於Spring XML中的<import/>元素。github

本文將對該註解進行介紹,並經過實例演示它導入配置類的四種方式,最後對該註解進行源碼解析。web

話很少說,走起~spring

簡介

@Import註解的全類名是org.springframework.context.annotation.Import。其只有一個默認的value屬性,該屬性類型爲Class<?>[],表示能夠傳入一個或多個Class對象。api

經過註釋能夠看出,該註解有以下做用:數組

  • 能夠導入一個或多個組件類(一般是@Configuration配置類)
  • 該註解的功能與Spring XML中的<import/>元素相同。能夠導入@Configuration配置類、ImportSelectImportBeanDefinitionRegistrar的實現類。從4.2版本開始,還能夠引用常規組件類(普通類),該功能相似於AnnotationConfigApplicationContext.register方法。
  • 該註解能夠在類中聲明,也能夠在元註解中聲明。
  • 若是須要導入XML或其餘非@Configuration定義的資源,可使用@ImportResource註釋。

導入配置類的四種方式

源碼註釋寫得很清楚,該註解有四種導入方式:微信

  1. 普通類
  2. @Configuration配置類
  3. ImportSelector的實現類
  4. ImportBeanDefinitionRegistrar的實現類

下面咱們逐個來介紹~markdown

準備工做

建立四個配置類: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方法是處理全部配置類的過程,其按下面步驟來處理:

  1. @Component註解
  2. @PropertySource註解
  3. @ComponentScan註解
  4. @Import註解
  5. @ImportResource註解
  6. @Bean註解
  7. 配置類的接口上的默認方法
  8. 配置類的超類
@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導入的類:

  1. ImportSelector類的處理
  2. ImportBeanDefinitionRegistrar類的處理
  3. 其它類統一按照@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,若對你有幫助,歡迎給個⭐,感謝~~🌹🌹🌹

github.com/gozhuyinglo…

推薦閱讀

關於做者

項目 內容
公衆號 碼農StayUp(ID:AcmenStayUp)
主頁 gozhuyinglong.github.io
CSDN blog.csdn.net/gozhuyinglo…
掘進 juejin.cn/user/123990…
Github github.com/gozhuyinglo…
Gitee gitee.com/gozhuyinglo…
相關文章
相關標籤/搜索