@Import註解 -【Spring底層原理】

blog50

1、註解用法

@Import註解也是用來給容器註冊組件的,使用@Import註解快速給容器中導入一個組件有三種方法java

  1. 導入@Configuration註解的配置類使用@Import(要導入到容器中的組件):容器中就會自動註冊這個組件,ID默認爲全類名
  2. 導入ImportSelector的實現類:經過實現ImportSelector類,實現selectImports方法,返回須要導入組件的全類名數組
  3. 導入ImportBeanDefinitionRegistrar的實現類:經過實現ImportBeanDefinitionRegistrar類,實現registerBeanDefinitions方法手動註冊Bean到容器中
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}
複製代碼

經過註解源碼能夠看到,@Import註解做用在類上,而且參數能夠是class類型的數組,從這裏能夠看出可使用@Import註解一次導入多個組件到容器中數組

2、實例分析

從上面的註解用法來看,使用@Import註解給容器導入組件有三種方法,而且該註解做用在方法上,一次能夠導入多個組件,所以,這裏咱們直接將三種方法都放在一個@Import註解來進行導入。以下案例需求:使用方法一注入User類、使用方法二注入Person類、使用方法三注入Animal類。markdown

【1】導入@Configuration註解的配置類使用@Importapp

// 啓動類,經過打印容器中的Bean來判斷是否注入
@Test
public void TestMain(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    String[] beanNames = applicationContext.getBeanDefinitionNames();
    for (String beanName : beanNames) {
        System.out.println(beanName);
    }
}

// 待注入的User
public class User {
}

// 配置類
@Configuration
@Import(User.class)     //使用@Import導入組件,ID默認是組件的全類名
public class AppConfig {
}
複製代碼

經過在配置類上使用@Import註解,將User給注入進容器中,運行啓動類,能夠看到容器中有User對象:oop

image-20210226164625069

【2】導入ImportSelector的實現類this

導入ImportSelector的實現類須要實現ImportSelector類,自定義邏輯返回須要導入的組件,返回的字符串數組便是要注入的組件,添加修改以下代碼:spa

// ImportSelector實現類
public class MyImportSelector implements ImportSelector {
    /** * @description 獲取要導入到容器的組件全類名 * @author ONESTAR * @date 2021/2/25 15:49 * @param annotationMetadata:當前標註@Import註解類的全部註解信息 * @throws * @return java.lang.String[] */
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"bean.Person"};
    }
}

// 待注入的Person
public class Person {
}

// 配置類
@Configuration
@Import({User.class, MyImportSelector.class})     //使用@Import導入組件,ID默認是組件的全類名
public class AppConfig {
}
複製代碼

ImportSelector實現類中獲取要導入到容器的組件全類名,這裏將ImportSelector實現類在配置類中使用@Import註解進行配置,運行啓動類,能夠看到容器中有Person對象:.net

image-20210227151242060

【3】導入ImportBeanDefinitionRegistrar的實現類code

導入ImportBeanDefinitionRegistrar的實現類須要實現ImportBeanDefinitionRegistrar類,經過實現registerBeanDefinitions方法手動註冊Bean到容器中,添加修改以下代碼:orm

// ImportBeanDefinitionRegistrar實現類
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

        // 指定Bean的名稱
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Animal.class);
        beanDefinitionRegistry.registerBeanDefinition("Animal", beanDefinition);
    }
}

// 待注入的Animal
public class Animal {
}

// 配置類
@Configuration
@Import({User.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})     //使用@Import導入組件,ID默認是組件的全類名
public class AppConfig {
}
複製代碼

經過ImportBeanDefinitionRegistrar的實現類進行手動註冊添加Bean,並在配置類中使用@Import註解進行配置,運行啓動類,能夠看到容器中有Animal對象:

image-20210227153057676

3、源碼追蹤

參考:blog.csdn.net/mamamalulul…

經過@Configuration註解,會進入到doProcessConfigurationClass方法,此時解析的是appConfigure,在doProcessConfigurationClass方法裏面,有個執行@Import註解的方法,即processImports

this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);
複製代碼

@Import註解執行的時機,解析配置類的時候,由ConfigurationClassParser當中的processImports來處理,在分析processImports方法以前,我們先來看看參數getImports方法:

【1】getImports方法

進入源碼查看方法,這個方法就是獲取全部的@import 裏面的類,流程以下:

  1. 定義一個 visited 的集合,用做 是否已經 判斷過的標誌
  2. 這裏就是獲取sourceClass 上面的 全部的 annotation,並挨個判斷, 若是不是 @import ,那就 進一步遞歸 調用 對應的 annotation,直到所有結束
  3. 加載sourceClass 裏面 的@Import annotation 裏面對應的類名 ,最後返回
// 獲取全部的`@import` 裏面的類
private Set<ConfigurationClassParser.SourceClass> getImports(ConfigurationClassParser.SourceClass sourceClass) throws IOException {
    Set<ConfigurationClassParser.SourceClass> imports = new LinkedHashSet();
    Set<ConfigurationClassParser.SourceClass> visited = new LinkedHashSet();
    this.collectImports(sourceClass, imports, visited);
    return imports;
}

// 這裏就是獲取sourceClass 上面的 全部的 annotation, 若是不是 @import ,那就 進一步遞歸 調用 對應的 annotation,直到所有結束
private void collectImports(ConfigurationClassParser.SourceClass sourceClass, Set<ConfigurationClassParser.SourceClass> imports, Set<ConfigurationClassParser.SourceClass> visited) throws IOException {
    if (visited.add(sourceClass)) {
        Iterator var4 = sourceClass.getAnnotations().iterator();

        while(var4.hasNext()) {
            ConfigurationClassParser.SourceClass annotation = (ConfigurationClassParser.SourceClass)var4.next();
            String annName = annotation.getMetadata().getClassName();
            if (!annName.equals(Import.class.getName())) {
                this.collectImports(annotation, imports, visited);
            }
        }

        imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
    }

}
複製代碼

【2】processImports 方法

獲取到@Import裏面的類後,再來執行processImports方法,進入源碼進行查看:

private void processImports(ConfigurationClass configClass, ConfigurationClassParser.SourceClass currentSourceClass, Collection<ConfigurationClassParser.SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) {
    // 準備注入的候選類集合爲空 直接返回
    if (!importCandidates.isEmpty()) {
        // 循環注入的檢查
        if (checkForCircularImports && this.isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new ConfigurationClassParser.CircularImportProblem(configClass, this.importStack));
        } else {
            // 當前configClass加入到ImportStack裏面
            this.importStack.push(configClass);

            try {
                Iterator var6 = importCandidates.iterator();

                // 遍歷注入的候選集合
                while(var6.hasNext()) {
                    ConfigurationClassParser.SourceClass candidate = (ConfigurationClassParser.SourceClass)var6.next();
                    Class candidateClass;
                    // 若是是實現了ImportSelector接口的類
                    if (candidate.isAssignable(ImportSelector.class)) {
                        candidateClass = candidate.loadClass();
                        ImportSelector selector = (ImportSelector)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) {
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);
                        } else {
                            // 調用selector當中的selectImports方法,獲得要注入的類的全限定名
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<ConfigurationClassParser.SourceClass> importSourceClasses = this.asSourceClasses(importClassNames, exclusionFilter);
                            this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                        }
                        // 若是是ImportBeanDefinitionRegistrar 則configClass.addImportBeanDefinitionRegistrar 提早放到一個map當中
                    } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    } else {
                        // 若是是普通類
                        this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        this.processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                    }
                }
            } catch (BeanDefinitionStoreException var17) {
                throw var17;
            } catch (Throwable var18) {
                throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", var18);
            } finally {
                this.importStack.pop();
            }
        }

    }
}
複製代碼

以上方法即是@Import註解的核心做用,能夠總結爲如下幾點:

  1. 判斷注入的候選集合 importCandidates 是否爲空,爲空則直接退出
  2. 循環注入檢查:判斷isChainedImportOnStack ,若是爲true ,加入 problemReporter 裏面的error ,並退出
  3. 把當前的 configClass 加入到 ImportStack裏面,ImportStack 是繼承了 ArrayDeque 和實現了 ImportRegistry
  4. 遍歷注入的候選集合:對 getImports 裏面獲取到的 須要import 的類 進行遍歷 處理(三種類型的類執行不一樣的邏輯)
    1. 實現了ImportSelector接口的類,調用getExclusionFilter()方法,若是不爲空,那麼就進行過濾,過濾後調用selectImports方法,獲得要注入的類的全限定名。根據類全限定名,獲得類元信息。而後遞歸的調用processImports方法
    2. 實現了ImportBeanDefinitionRegistrar接口的類,會實例化這個類,放入集合importBeanDefinitionRegistrars當中
    3. 普通類型的類(上面兩個都不知足),那麼就把它看成是配置類來處理,調用processConfigurationClass方法,最終會放入到configurationClasses這個集合當中。
  5. 若是是 ImportBeanDefinitionRegistrar 類型,這裏也是 先實例一個對象,而後加入到 importBeanDefinitionRegistrars 裏面,後續 會在 ConfigurationClassBeanDefinitionReader 這個類裏面 的 loadBeanDefinitionsFromRegistrars 方法處理的
  6. 若是上面兩種類型都不是,那就是當初普通的 帶有@Configuration 的類進行處理了
相關文章
相關標籤/搜索