《Spring核心技術》第5章:三萬字深度解析@Import註解

ITPUB社群發表於2023-02-27

作者:冰河
星球:
部落格:
文章彙總:/md/all/all.html
原始碼地址:

沉澱,成長,突破,幫助他人,成就自我。

大家好,我是冰河~~


  • 「本章難度」:★★★★☆
  • 「本章重點」:進一步學習並掌握@Import註解向Spring IOC容器中注入Bean的示例與流程,從原始碼級別徹底掌握@Import註解在Spring底層的執行流程。

本節目錄如下所示:

  • 學習指引
  • 註解說明
    • 註解原始碼
    • 註解使用場景
  • 使用案例
    • 引入普通類案例
    • 引入實現了ImportSelector介面的類案例
    • 引入實現了ImportBeanDefinitionRegistrar介面的類案例
  • 原始碼時序圖
  • 原始碼解析
  • 總結
  • 思考
  • VIP服務

一、學習指引

@Import註解是什麼?

想深入學習一項技術並不是一朝一夕就能夠完成的,它需要我們花費大量的時間和精力,塌下心來深入研究,從不知道,到了解,再到熟悉,最後到精通,這需要一個不斷深入研究,不斷實踐的過程。

學習Spring亦是如此,要想掌握好Spring的核心技術,同樣需要塌下心來不斷研究和實踐。

二、註解說明

關於@Import註解的一點點說明~~

@Import註解可以將第三方包中的類物件注入到IOC容器中。使用Spring開發業務系統時,@Import註解的使用頻率不及@Bean註解,@Import註解往往在一些中介軟體或者框架專案中使用的比較多。

在Spring底層,也大量使用了@Import註解來向IOC容器中注入Bean物件。當然,如果在開發業務系統時,也可以使用@Import註解向IOC容器中注入Bean物件。@Import註解相比於@Bean註解來講,在使用上會更加靈活。

2.1 註解原始碼

@Import註解只能標註到類或其他註解上,通常與配置類一起使用的,使用此註解引入的類上可以不再使用@Configuration,@Component等註解標註。本節,就對@Import註解的原始碼進行簡單的剖析。

@Import註解的原始碼詳見:org.springframework.context.annotation.Import,如下所示。

/**
 * Since: 3.0
 * @author Chris Beams
 * @author Juergen Hoeller
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
 Class<?>[] value();
}

從@Import原始碼的註釋可以看出,@Import是Spring從3.0版本開始提供的註解,註解中只有一個Class陣列型別的value屬性。含義如下所示。

  • value:Class陣列型別,用於指定其他配置類的位元組碼,支援指定多個配置類。另外,使用value屬性指定的有一定的條件,必須是普通類、實現了ImportSelector介面的類和實現了ImportBeanDefinitionRegistrar介面的類。

注意:@Import註解只能標註到類上。

2.2 註解使用場景

在使用Spring進行開發時,如果涉及到的配置項比較多,要是將所有的配置項都寫到一個類裡,則配置結構和配置內容將會變得非常雜亂,如果此時使用@Import註解,則可以將配置項進行分類管理。

另外,如果在專案中需要引入第三方的類,並且需要將這些類的物件注入到IOC容器中,也可以使用@Import註解。

三、使用案例

@Import註解案例實戰~~

@Import註解可以引入三種類,分別如下所示。

  • 引入普通類,將Bean物件注入到IOC容器。
  • 引入實現了ImportSelector介面的類,將selectImports()方法返回的Bean陣列注入到IOC容器,但是實現了ImportSelector介面的類物件本身不會被註冊到IOC容器中。
  • 引入實現了ImportBeanDefinitionRegistrar介面的類,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry物件注入BeanDefinition物件到IOC容器中,但是實現了ImportBeanDefinitionRegistrar介面的類物件本身不會被註冊到IOC容器中。

3.1 引入普通類案例

本節,主要實現使用@Import註解實現引入普通類,並且將Bean物件注入到IOC容器中的案例。具體實現步驟如下所示。

(1)新建User類

User類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.bean.User,如下所示。

public class User {
    private Long userId;
    private String userName;
    //#############省略getter/serrer方法############
}

可以看到,User類就是一個普通的類物件,後續會透過@Import註解引入User類,並且將User類的物件注入到IOC容器中。

(2)新建Spring配置類ImportConfig

ImportConfig類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.config.ImportConfig,如下所示。

@Import(value = {User.class})
@Configuration
public class ImportConfig 
{
}

可以看到,ImportConfig類主要是Spring的配置類,會在ImportConfig類上標註@Configuration註解和@Import註解,並且會透過@Import註解引入User類,將User類的物件注入到IOC容器中。

(3)新建ImportTest類

ImportTest類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.ImportTest,如下所示。

public class ImportTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportConfig.class);
        String[] definitionNames = context.getBeanDefinitionNames();
        Arrays.stream(definitionNames).forEach((definitionName) -> System.out.println(definitionName));
    }
}

可以看到,ImportTest類主要是案例的測試類,在ImportTest類的main()方法中,主要列印了Bean定義的名稱。

(4)執行ImportTest類

執行ImportTest類的main()方法,輸出的結果資訊如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User

其中,以org.springframework包命名的Bean是Spring內部的Bean。另外,可以看到,結果資訊中也輸出了ImportConfig類的Bean名稱和User類的Bean名稱。

說明:使用@Import註解可以引入普通的類,並且能夠將類物件注入到Spring容器中。

3.2 引入實現了ImportSelector介面的類案例

本節,主要實現使用@Import註解引入實現了ImportSelector介面的類,將selectImports()方法返回的Bean陣列注入到IOC容器中的案例。具體的實現步驟如下所示。

注意:本節實現的案例是在3.1節的基礎上實現的。

(1)新建ImportSelectorBean類

ImportSelectorBean類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean,如下所示。

public class ImportSelectorBean {
    private Long id;
    private String name;
    //########省略getter/setter方法#########
}

可以看到,ImportSelectorBean類是一個普通的類,ImportSelectorBean類的物件後續會透過ImportSelector介面的selectImports()注入到IOC容器中。

(2)新建MyImportSelector類

MyImportSelector類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.selector.MyImportSelector,如下所示。

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ImportSelectorBean.class.getName()};
    }
}

可以看到,MyImportSelector類實現了ImportSelector介面,並實現了ImportSelector介面的selectImports()方法,在selectImports()中返回了包含ImportSelectorBean類的全類名的Spring陣列。

(3)修改ImportConfig類

ImportConfig類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.config.ImportConfig,如下所示。

@Import(value = {User.classMyImportSelector.class})
@Configuration
public class ImportConfig 
{
}

可以看到,在ImportConfig類上標註的@Import註解的value屬性中,新增MyImportSelector類的Class物件。

(4)執行ImportTest類

執行ImportTest類的main()方法,輸出的結果資訊如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean

可以看到,在輸出的結果資訊中,除了有Spring內部的Bean物件的名稱、ImportConfig類的Bean物件名稱和User類的Bean物件名稱外,還輸出了ImportSelectorBean類的Bean物件名稱。但是,並沒有輸出實現了ImportSelector介面的MyImportSelector類的Bean物件的名稱。

說明:使用@Import註解可以引入實現了ImportSelector介面的類,將selectImports()方法返回的Bean陣列注入到IOC容器中,但是實現了ImportSelector介面的類物件本身不會被註冊到IOC容器中。

3.3 引入實現了ImportBeanDefinitionRegistrar介面的類案例

本節,主要實現使用@Import註解引入實現了ImportBeanDefinitionRegistrar介面的類,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry物件注入BeanDefinition物件到IOC容器中的案例。具體實現步驟如下所示。

(1)新增ImportBeanDefinitionRegistrarBean類

ImportBeanDefinitionRegistrarBean類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.bean.ImportBeanDefinitionRegistrarBean,如下所示。

public class ImportBeanDefinitionRegistrarBean {
    private Long id;
    private String name;
 //#########省略getter/setter方法############
}

可以看到,ImportBeanDefinitionRegistrarBean類就是一個普通的類,後續會透過ImportBeanDefinitionRegistrar介面的實現類實現的registerBeanDefinitions()方法將ImportBeanDefinitionRegistrarBean類的Bean物件注入到IOC容器中。

(2)新增MyImportBeanDefinitionRegistrar類

MyImportBeanDefinitionRegistrar類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.registrar.MyImportBeanDefinitionRegistrar,如下所示。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        String beanName = ImportBeanDefinitionRegistrarBean.class.getName();
        BeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarBean.class);
        registry.registerBeanDefinition(beanName, beanDefinition);
    }
}

可以看到,MyImportBeanDefinitionRegistrar類實現了ImportBeanDefinitionRegistrar介面,並實現了ImportBeanDefinitionRegistrar介面的registerBeanDefinitions()方法。在registerBeanDefinitions()方法中,獲取ImportBeanDefinitionRegistrarBean類的全類名作為注入到IOC容器中的Bean名稱。接下來,呼叫RootBeanDefinition類的構造方法傳入ImportBeanDefinitionRegistrarBean類的Class物件建立BeanDefinition物件。最終,呼叫registry的registerBeanDefinition()方法將建立的BeanDefinition物件注入到IOC容器中。

(3)修改ImportConfig類

ImportConfig類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.config.ImportConfig,如下所示。

@Import(value = {User.classMyImportSelector.classMyImportBeanDefinitionRegistrar.class})
@Configuration
public class ImportConfig 
{
}

可以看到,在ImportConfig類上標註的@Import註解的value屬性中,新增了實現了ImportBeanDefinitionRegistrar介面的MyImportBeanDefinitionRegistrar類的Class物件。

(4)執行ImportTest類

執行ImportTest類的main()方法,輸出的結果資訊如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean
io.binghe.spring.annotation.chapter05.bean.ImportBeanDefinitionRegistrarBean

可以看到,在輸出的結果資訊中,除了Spring內部的Bean名稱、ImportConfig類的Bean名稱、User類的Bean名稱和ImportSelectorBean類的Bean名稱外,還輸出了ImportBeanDefinitionRegistrarBean類的名稱。但是並沒有輸出實現了ImportBeanDefinitionRegistrar介面的MyImportBeanDefinitionRegistrar類的Bean名稱。

說明:使用@Import註解能夠引入實現了ImportBeanDefinitionRegistrar介面的類,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry物件注入BeanDefinition物件到IOC容器中,但是實現了ImportBeanDefinitionRegistrar介面的類物件本身不會被註冊到IOC容器中。

四、原始碼時序圖

結合時序圖理解原始碼會事半功倍,你覺得呢?

本節,就以原始碼時序圖的方式,直觀的感受下@Import註解在Spring原始碼層面的執行流程。@Import註解在Spring原始碼層面的執行流程如圖5-1~5-3所示。

《Spring核心技術》第5章:三萬字深度解析@Import註解

《Spring核心技術》第5章:三萬字深度解析@Import註解

《Spring核心技術》第5章:三萬字深度解析@Import註解

由圖5-1~圖5-3可以看出,@Import註解在Spring原始碼層面的執行流程會涉及到ImportTest類、AnnotationConfigApplicationContext類、AbstractApplicationContext類、PostProcessorRegistrationDelegate類、ConfigurationClassPostProcessor類、ConfigurationClassParser類、MyImportSelector類、ConfigurationClassBeanDefinitionReader類、ImportBeanDefinitionRegistrar類、MyImportBeanDefinitionRegistrar類和DefaultListableBeanFactory類。具體的原始碼執行細節參見原始碼解析部分。

五、原始碼解析

原始碼時序圖整清楚了,那就整原始碼解析唄!

@Import註解在Spring原始碼層面的執行流程,結合原始碼執行的時序圖,會理解的更加深刻。

(1)執行案例程式啟動類

案例程式啟動類原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.ImportTest,執行ImportTest類的main()方法。

在ImportTest類的main()方法中呼叫了AnnotationConfigApplicationContext類的構造方法,並傳入了ImportConfig類的Class物件來建立IOC容器。接下來,會進入AnnotationConfigApplicationContext類的構造方法。

(2)解析AnnotationConfigApplicationContext類的AnnotationConfigApplicationContext(Class<?>... componentClasses)構造方法

原始碼詳見:org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class<?>... componentClasses)。

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

可以看到,在上述構造方法中,呼叫了refresh()方法來重新整理IOC容器。

(3)解析AbstractApplicationContext類的refresh()方法

原始碼詳見:org.springframework.context.support.AbstractApplicationContext#refresh()。

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //############省略其他程式碼##############
        try {
            //############省略其他程式碼##############
            invokeBeanFactoryPostProcessors(beanFactory);
           //############省略其他程式碼##############
        }catch (BeansException ex) {
            //############省略其他程式碼##############
        }finally {
            //############省略其他程式碼##############
        }
    }
}

refresh()方法是Spring中一個非常重要的方法,很多重要的功能和特性都是透過refresh()方法進行注入的。可以看到,在refresh()方法中,呼叫了invokeBeanFactoryPostProcessors()方法。

(4)解析AbstractApplicationContext類的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)方法

原始碼詳見:org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)。

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
}

可以看到,在AbstractApplicationContext類的invokeBeanFactoryPostProcessors()方法中呼叫了PostProcessorRegistrationDelegate類的invokeBeanFactoryPostProcessors()方法。

(5)解析PostProcessorRegistrationDelegate類的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List

原始碼詳見:org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List

由於方法的原始碼比較長,這裡,只關注當前最核心的邏輯,如下所示。

public static void invokeBeanFactoryPostProcessors(
    ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors)
 
{

    //############省略其他程式碼##############
    List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

    // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
    String[] postProcessorNames =
        beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.classtruefalse);
    for (String ppName : postProcessorNames) {
        if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
        }
    }
    sortPostProcessors(currentRegistryProcessors, beanFactory);
    registryProcessors.addAll(currentRegistryProcessors);
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
    currentRegistryProcessors.clear();

    // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
    postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.classtruefalse);
    for (String ppName : postProcessorNames) {
        if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
        }
    }
    sortPostProcessors(currentRegistryProcessors, beanFactory);
    registryProcessors.addAll(currentRegistryProcessors);
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
    currentRegistryProcessors.clear();

    // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
    boolean reiterate = true;
    while (reiterate) {
        reiterate = false;
        postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.classtruefalse);
        for (String ppName : postProcessorNames) {
            if (!processedBeans.contains(ppName)) {
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                processedBeans.add(ppName);
                reiterate = true;
            }
        }
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
        currentRegistryProcessors.clear();
    }
    //############省略其他程式碼##############
}

可以看到,在PostProcessorRegistrationDelegate類的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List

(6)解析PostProcessorRegistrationDelegate類的invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup)方法

原始碼詳見:org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup)。

private static void invokeBeanDefinitionRegistryPostProcessors(
    Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup)
 
{

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
            .tag("postProcessor", postProcessor::toString);
        postProcessor.postProcessBeanDefinitionRegistry(registry);
        postProcessBeanDefRegistry.end();
    }
}

可以看到,在invokeBeanDefinitionRegistryPostProcessors()方法中,會迴圈遍歷postProcessors集合中的每個元素,呼叫postProcessBeanDefinitionRegistry()方法註冊Bean的定義資訊。

(7)解析ConfigurationClassPostProcessor類的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法

原始碼詳見:org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)。

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
 //##########省略其他程式碼###################
    processConfigBeanDefinitions(registry);
}

可以看到,在postProcessBeanDefinitionRegistry()方法中,會呼叫processConfigBeanDefinitions()方法。

(8)解析ConfigurationClassPostProcessor類的processConfigBeanDefinitions(BeanDefinitionRegistry registry)方法

原始碼詳見:org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions(BeanDefinitionRegistry registry)。

這裡,重點關注方法中的如下邏輯。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    //############省略其他程式碼#################
    // Parse each @Configuration class
    ConfigurationClassParser parser = new ConfigurationClassParser(
        this.metadataReaderFactory, this.problemReporter, this.environment,
        this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
        parser.parse(candidates);
        parser.validate();
        //############省略其他程式碼#################
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);
        processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
        //############省略其他程式碼#################
    }
    while (!candidates.isEmpty());
    //############省略其他程式碼#################
}

可以看到,在processConfigBeanDefinitions()方法中,建立了一個ConfigurationClassParser型別的物件parser,並且呼叫了parser的parse()方法來解析類的配置資訊。

(9)解析ConfigurationClassParser類的parse(Set

原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#parse(Set

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            if (bd instanceof AnnotatedBeanDefinition) {
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            //###############省略其他程式碼###############
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }
    this.deferredImportSelectorHandler.process();
}

可以看到,在ConfigurationClassParser類的parse(Set

(10)解析ConfigurationClassParser類的parse(AnnotationMetadata metadata, String beanName)方法

原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#parse(AnnotationMetadata metadata, String beanName)

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

可以看到,上述parse()方法的實現比較簡單,直接呼叫了processConfigurationClass()方法。

(11)解析ConfigurationClassParser類的processConfigurationClass(ConfigurationClass configClass, Predicate

原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass(ConfigurationClass configClass, Predicate

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    //###############省略其他程式碼####################
    SourceClass sourceClass = asSourceClass(configClass, filter);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    }
    while (sourceClass != null);
    this.configurationClasses.put(configClass, configClass);
}

可以看到,在processConfigurationClass()方法中,會透過do-while()迴圈獲取配置類和其父類的註解資訊,SourceClass類中會封裝配置類上註解的詳細資訊。在在processConfigurationClass()方法中,呼叫了doProcessConfigurationClass()方法。

(12)解析ConfigurationClassParser類的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate

原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)

    throws IOException 
{
  //#############省略其他程式碼#############
    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    //#############省略其他程式碼#############
    // No superclass -> processing is complete
    return null;
}

可以看到,在doProcessConfigurationClass()方法中,會呼叫processImports()方法來解析@Import註解。

(13)解析ConfigurationClassParser類的processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection

原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
                            boolean checkForCircularImports)
 
{
 //################省略其他程式碼#################
    this.importStack.push(configClass);
    try {
        for (SourceClass candidate : importCandidates) {
            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.classthis.environmentthis.resourceLoaderthis.registry);
                Predicate<String> selectorFilter = selector.getExclusionFilter();
                if (selectorFilter != null) {
                    exclusionFilter = exclusionFilter.or(selectorFilter);
                }
                if (selector instanceof DeferredImportSelector) {
                    this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                }
                else {
                    String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                    Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                    processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                }
            }
            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.environmentthis.resourceLoaderthis.registry)
;
                configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
            }
            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.getMessage(), ex);
    }
    finally {
        this.importStack.pop();
    }
}

在processImports()方法中,如果使用@Import註解引入的是實現了ImportSelector介面的類,則執行的是if (candidate.isAssignable(ImportSelector.class))條件的邏輯。如果@Import註解引入的是實現了ImportBeanDefinitionRegistrar介面的類,則執行的是 else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))條件的邏輯,否則執行的是else條件的邏輯。

其中,執行if (candidate.isAssignable(ImportSelector.class))條件的邏輯時,會呼叫ImportSelector介面的selectImports()方法獲取要注入到IOC容器中的Bean名稱陣列,如下所示。

String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());

呼叫ImportSelector介面的selectImports()方法時,就會呼叫案例程式中的MyImportSelector類的selectImports()方法。

(14)解析MyImportSelector類的selectImports(AnnotationMetadata importingClassMetadata)方法

原始碼詳見:io.binghe.spring.annotation.chapter05.selector.MyImportSelector#selectImports(AnnotationMetadata importingClassMetadata)

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    return new String[]{ImportSelectorBean.class.getName()};
}

可以看到,在MyImportSelector類的selectImports()方法中,會返回包含ImportSelectorBean類的全類名的String陣列,後續會將ImportSelectorBean類的Bean物件注入IOC容器。

(15)回到ConfigurationClassParser類的processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection

如果@Import註解引入的是實現了ImportBeanDefinitionRegistrar介面的類,則執行的是 else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))條件的邏輯,如下所示。

else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
    Class<?> candidateClass = candidate.loadClass();
    ImportBeanDefinitionRegistrar registrar =
        ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.classthis.environmentthis.resourceLoaderthis.registry);
    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}

可以看到,在上述程式碼邏輯中會呼叫configClass的addImportBeanDefinitionRegistrar()方法來新增ImportBeanDefinitionRegistrar物件。

(16)解析ConfigurationClass類的addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata)方法

原始碼詳見:org.springframework.context.annotation.ConfigurationClass#addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata)。

void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
    this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}

可以看到,在addImportBeanDefinitionRegistrar()方法中,會將傳入的registrar引數作為Key,importingClassMetadata引數作為Value儲存importBeanDefinitionRegistrars結構中。

其中,importBeanDefinitionRegistrars結構的定義如下所示。

private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();

可以看到,importBeanDefinitionRegistrars是一個LinkedHashMap物件,也就是說,會將ImportBeanDefinitionRegistrar物件和AnnotationMetadata物件的對映關係存入一個LinkedHashMap物件中。

(17)再次回到ConfigurationClassParser類的processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection

如果@Import註解引入的類既沒有實現ImportSelector介面,又沒有實現ImportBeanDefinitionRegistrar介面,則執行else邏輯,如下所示。

else {
    this.importStack.registerImport(
        currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
    processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}

else邏輯中,會按照解析@Configuration註解的邏輯執行,有關@Configuration註解的執行流程,可以參見第1章的內容,這裡不再贅述。

其實,在processImports()方法中,如果@Import註解引入的類實現了ImportSelector介面,並且沒有實現DeferredImportSelector介面的話,最終還是會執行processImports()方法的else邏輯。

(18)回到ConfigurationClassPostProcessor類的processConfigBeanDefinitions(BeanDefinitionRegistry registry)方法。

在ConfigurationClassPostProcessor類的processConfigBeanDefinitions()方法中,執行完ConfigurationClassParser類的parse()方法後,會執行ConfigurationClassBeanDefinitionReader類的loadBeanDefinitions()方法,如下所示。

this.reader.loadBeanDefinitions(configClasses);

(19)解析ConfigurationClassBeanDefinitionReader類的loadBeanDefinitions(Set

原始碼詳見:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions(Set

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    for (ConfigurationClass configClass : configurationModel) {
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
}

可以看到,在loadBeanDefinitions()方法中,會迴圈遍歷傳入的configurationModel集合,並呼叫loadBeanDefinitionsForConfigurationClass()方法處理遍歷的每個元素。

(20)解析ConfigurationClassBeanDefinitionReader類的loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)方法

原始碼詳見:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)。

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    //################省略其他程式碼######################
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }

    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

在loadBeanDefinitionsForConfigurationClass()方法中,如果@Import註解引入的是普通的類,或者是實現了ImportSelector介面的類,則會執行if (configClass.isImported())條件的邏輯,此時會呼叫registerBeanDefinitionForImportedConfigurationClass()方法向IOC容器中注入配置類的BeanDefinition資訊。

(21)解析ConfigurationClassBeanDefinitionReader類的registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass)方法

原始碼詳見:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass),如下所示。

private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
    //###############省略其他程式碼#################
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  this.registry.registerBeanDefinition(definitionHolder.getBeanName(),definitionHolder.getBeanDefinition());
    configClass.setBeanName(configBeanName);
 //###############省略其他程式碼#################
}

可以看到,在registerBeanDefinitionForImportedConfigurationClass()方法中會呼叫DefaultListableBeanFactory類的registerBeanDefinition()方法向IOC容器中注入BeanDefinition資訊。最終,會將BeanDefinition資訊儲存到DefaultListableBeanFactory類的beanDefinitionMap中。

(22)回到ConfigurationClassBeanDefinitionReader類的loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)方法

如果@Import註解引入的是實現了ImportBeanDefinitionRegistrar介面的類,則在loadBeanDefinitionsForConfigurationClass()方法中呼叫loadBeanDefinitionsForConfigurationClass()方法時,會透過configClass的getImportBeanDefinitionRegistrars()方法獲取第(16)步儲存資訊的LinkedHashMap物件。

(23)解析ConfigurationClass類的getImportBeanDefinitionRegistrars()方法

原始碼詳見:org.springframework.context.annotation.ConfigurationClass#getImportBeanDefinitionRegistrars()。

Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> getImportBeanDefinitionRegistrars() {
    return this.importBeanDefinitionRegistrars;
}

(24)再次ConfigurationClassBeanDefinitionReader類的loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)方法。

在loadBeanDefinitionsForConfigurationClass()會呼叫loadBeanDefinitionsFromRegistrars()方法從實現了ImportBeanDefinitionRegistrar介面的類中載入Bean定義資訊。

(25)解析ConfigurationClassBeanDefinitionReader類的loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars)方法

原始碼詳見:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars)。

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

可以看到,在loadBeanDefinitionsFromRegistrars()方法中,會遍歷傳入的registrars,並呼叫每個registrar的registerBeanDefinitions()方法註冊BeanDefinition資訊。

(26)解析ImportBeanDefinitionRegistrar介面的registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator)方法

原始碼詳見:org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator)

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
    registerBeanDefinitions(importingClassMetadata, registry);
}

可以看到,registerBeanDefinitions()方法是ImportBeanDefinitionRegistrar介面的一個預設方法,並在方法中呼叫了另一個registerBeanDefinitions()方法。其中,呼叫的這個registerBeanDefinitions()方法就是我們自己寫的案例中實現了ImportBeanDefinitionRegistrar介面的MyImportBeanDefinitionRegistrar類中的方法。

(27)解析MyImportBeanDefinitionRegistrar類的registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)方法

原始碼詳見:io.binghe.spring.annotation.chapter05.registrar.MyImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)。

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    String beanName = ImportBeanDefinitionRegistrarBean.class.getName();
    BeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarBean.class);
    registry.registerBeanDefinition(beanName, beanDefinition);
}

可以看到,在registerBeanDefinitions()方法中,最終會呼叫DefaultListableBeanFactory類的registerBeanDefinition()方法向IOC容器中注入BeanDefinition資訊。最終,會將BeanDefinition資訊儲存到DefaultListableBeanFactory類的beanDefinitionMap中。

至此,整個@Import註解在Spring原始碼層面的執行流程分析完畢。

六、總結

@Import註解講完了,我們一起總結下吧!

本章,首先介紹了@Import註解的原始碼和使用場景,並列舉了使用@Import註解向IOC容器中注入Bean物件的三種案例。接下來,詳細分析了@Import註解的原始碼時序圖和@Import註解在Spring原始碼層面的執行流程。

七、思考

既然學完了,就開始思考幾個問題吧?

關於@Import註解,通常會有如下幾個經典面試題:

1.在ConfigurationClassParser類的processImports()中,如果@Import註解引入的是普通類或者引入的是實現了ImportSelector介面,並且沒有實現DeferredImportSelector介面的類,最終還是會執行processImports()方法的else邏輯。那麼,如果@Import註解引入的是實現了ImportSelector介面,並且沒有實現DeferredImportSelector介面的類,最終是如何執行else邏輯的?

2.@Import註解的三種案例在Spring底層的原始碼執行流程分別是什麼?

3.使用@Import註解向IOC容器中注入Bean與使用@Bean註解有什麼區別?

4.在你自己負責的專案中,會在哪些場景下使用@Import註解向IOC容器中注入Bean?

5.你從Spring的@Import註解的設計中得到了哪些啟發?

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2937157/,如需轉載,請註明出處,否則將追究法律責任。

相關文章