天天看點

Spring注解[email protected]注解

今日在檢視springboot文檔時看到

None of these features are mandatory and you may choose to replace this single annotation by any of the features that it enables. For instance, you may not want to use component scan in your application:

package com.example.myapplication;

import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@EnableAutoConfiguration
@Import({ MyConfig.class, MyAnotherConfig.class })
public class Application {

public static void main(String[] args) {
   	SpringApplication.run(Application.class, args);
}

}
           

In this example, Application is just like any other Spring Boot application except that @Component-annotated classes are not detected automatically and the user-defined beans are imported explicitly (see @Import).

在檢視springboot文檔時看到

None of these features are mandatory and you may choose to replace this single annotation by any of the features that it enables. For instance, you may not want to use component scan in your application:

package com.example.myapplication;import org.springframework.boot.SpringApplication;import org.springframework.context.annotation.ComponentScanimport org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;@[email protected]@Import({ MyConfig.class, MyAnotherConfig.class })public class Application {public static void main(String[] args) { SpringApplication.run(Application.class, args);}}

In this example, Application is just like any other Spring Boot application except that @Component-annotated classes are not detected automatically and the user-defined beans are imported explicitly (see @Import).

初探

初始化spring boot,包含web即可

分别建立類

public class Aaaaaaa {
    public Aaaaaaa(){
        System.out.println("Aaaaaaa is init   *********************************");
    }
}

public class Bbbbbbb {
    public Bbbbbbb(){
        System.out.println("Bbbbbbb is init ***********************************");
    }
}

@Configuration
@Import(
.class)
public class MyConfig {

    public MyConfig(){
        System.out.println("MyConfig     ***********************************************");
    }

    @Bean
    public Aaaaaaa aaaaaaa(){
        Aaaaaaa aa=new Aaaaaaa();
        System.out.println("MyConfig   aaaaaaa  ***********************************************");
        return aa;
    }
}
           

運作應用将得到如下輸出

2019-09-18 17:06:27.132  INFO 14084 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-09-18 17:06:27.133  INFO 14084 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1093 ms
MyConfig     ***********************************************
Bbbbbbb is init ***********************************
Aaaaaaa is init   *********************************
MyConfig   aaaaaaa  ***********************************************
           

通過輸出将發現@Import導入的類被spring加載并建立了,且多次運作都會發現Bbbbbbb的執行個體化是在Aaaaaaa之前的,在MyConfig之後的,将@Import(Bbbbbbb.class)改至Aaaaaaa上

@Import(Bbbbbbb.class)
public class Aaaaaaa {
    public Aaaaaaa(){
        System.out.println("Aaaaaaa is init   *********************************");
    }
}
           

會發現Bbbbbbb并未建立

專業點的說明:

本章由以下幾部分組成:

  1. 從Enable字首的注解談起,揭示常見的Enable注解與Import注解的關系;
  2. 常見的四種Import注解用法列舉;
  3. 分析spring源碼,揭示Import注解的工作原理;
  4. 官方API文檔中的疑問解答;
  5. 實戰通過Import注解動态建立bean執行個體;

從Enable字首的注解談起

有很多注解都以Enable為字首,例如配置異步調用的注解EnableAsync,其源碼如下 :

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
    Class<? extends Annotation> annotation() default Annotation.class;

    AdviceMode mode() default AdviceMode.PROXY;

    int order() default Ordered.LOWEST_PRECEDENCE;
}
           

從以上代碼可見,使異步調用生效的關鍵是@Import(AsyncConfigurationSelector.class),通過此注解spring容器會建立AsyncConfigurationSelector執行個體并調用其selectImports方法,完成異步調用相關的配置;

再多看幾個Enable字首的注解的源碼,例如EnableBatchProcessing、EnableCaching、EnableDiscoveryClient等,也都是通過Import來生效的,這種方式值得我們學習,在業務開發中也能用類似方式來對bean執行個體做控制;

常見的四種Import注解用法列舉

在@Import注解的參數中可以填寫類名,例如@Import(Abc.class),根據類Abc的不同類型,spring容器有以下四種處理方式:

  1. 如果Abc類實作了ImportSelector接口,spring容器就會執行個體化Abc類,并且調用其selectImports方法;
  2. DeferredImportSelector是ImportSelector的子類,如果Abc類實作了DeferredImportSelector接口,spring容器就會執行個體化Abc類,并且調用其selectImports方法,和ImportSelector的執行個體不同的是,DeferredImportSelector的執行個體的selectImports方法調用時機晚于ImportSelector的執行個體,要等到@Configuration注解中相關的業務全部都處理完了才會調用(具體邏輯在ConfigurationClassParser.processDeferredImportSelectors方法中),想了解更多DeferredImportSelector和ImportSelector的差別,請參考《ImportSelector與DeferredImportSelector的差別(spring4) 》;
  3. 如果Abc類實作了ImportBeanDefinitionRegistrar接口,spring容器就會執行個體化Abc類,并且調用其registerBeanDefinitions方法;
  4. 如果Abc沒有實作ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar等其中的任何一個,spring容器就會執行個體化Abc類,官方說明在這裡;
分析spring源碼,揭示Import注解的工作原理

接下來通過spring源碼來了解spring容器是如何處理Import注解的;

  1. 先看spring容器的初始化代碼,定位AbstractApplicationContext類的refresh方法,裡面會調用invokeBeanFactoryPostProcessors方法,如下圖紅框所示:
    Spring注解[email protected]注解
  2. 展開invokeBeanFactoryPostProcessors方法,繼續追蹤到了PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法,具體操作如下圖紅框所示:
    Spring注解[email protected]注解
  3. 對于上圖分析的調用invokeBeanDefinitionRegistryPostProcessors方法時作為入參傳入的bean,ConfigurationClassPostProcessor類的執行個體是符合過濾要求的:既實作了BeanDefinitionRegistryPostProcessor接口,又實作了PriorityOrdered接口,是以,在invokeBeanDefinitionRegistryPostProcessors方法中,ConfigurationClassPostProcessor類的postProcessBeanDefinitionRegistry方法被調用:
  4. ConfigurationClassPostProcessor類的postProcessBeanDefinitionRegistry方法中,如下圖紅框所示,processConfigBeanDefinitions方法負責處理@Configuration注解相關的業務:
    Spring注解[email protected]注解
  5. processConfigBeanDefinitions方法代碼如下,請注意中文注釋:
//被确認為配置類的bean定義都放在集合configCandidates中
        Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
        //取所有bean的名稱
        String[] candidateNames = registry.getBeanDefinitionNames();
        //逐個檢查每個bean
        for (String beanName : candidateNames) {
            //取得每個bean的定義對象
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            //注意:ConfigurationClassUtils.checkConfigurationClassCandidate方法非常值得一看,裡面的通過目前類的注解來判斷是否為配置類
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                //例如有@Configuration注解的類,被判定為配置類,放入集合configCandidates中
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }

        // 如果一個配置類都沒找到,就直接傳回了
        if (configCandidates.isEmpty()) {
            return;
        }

        // Detect any custom bean name generation strategy supplied through the enclosing application context
        SingletonBeanRegistry singletonRegistry = null;
        if (registry instanceof SingletonBeanRegistry) {
            singletonRegistry = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet && singletonRegistry.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
                BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator;
            }
        }

        //執行個體化ConfigurationClassParser對象,用來處理配置類
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);

        Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
        do {
            //parse方法是處理配置類邏輯的核心代碼
            parser.parse(configCandidates);
            parser.validate();
           
  1. 看ConfigurationClassParser類的parse方法:
public void parse(Set<BeanDefinitionHolder> configCandidates) {
        //稍後執行的parse方法中,所有DeferredImportSelector實作類都會被放入集合deferredImportSelectors中
        this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();

        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                    //在這個parse方法中,所有DeferredImportSelector實作類都會被放入集合deferredImportSelectors中,它們的selectImports方法不會被執行,而其他ImportSelector實作類的selectImports都會被執行
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
                else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                    parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
                }
                else {
                    parse(bd.getBeanClassName(), holder.getBeanName());
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Exception ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
            }
        }
        //此方法内,會将集合deferredImportSelectors中的所有對象取出來執行其selectImports方法
        processDeferredImportSelectors();
    }
           
  1. 從上述代碼中可以看出,DeferredImportSelector實作類的selectImports方法會在最後被調用,其餘的關鍵邏輯應該在parse(AnnotationMetadata metadata, String beanName)這個關鍵方法中,順着這個方法一直追蹤下去,直到doProcessConfigurationClass方法,如下圖紅框所示,所有Import注解的處理,都在processImports方法中:
    Spring注解[email protected]注解
  2. processImports方法中包含了對ImportSelector實作類和ImportBeanDefinitionRegistrar實作類的處理,以及未實作這些接口的類的處理,我們逐個來分析吧,首先看ImportBeanDefinitionRegistrar實作類的處理,如下圖紅框位置,調用configClass.addImportBeanDefinitionRegistrar方法将ImportBeanDefinitionRegistrar實作類存入configClass的成員變量importBeanDefinitionRegistrars中,後面的ConfigurationClassPostProcessor類的processConfigBeanDefinitions方法中,this.reader.loadBeanDefinitions(configClasses);會調用這些ImportBeanDefinitionRegistrar實作類的registerBeanDefinitions方法:
    Spring注解[email protected]注解
  3. 再來看processImports方法中對ImportSelector實作類的處理,這裡略有些複雜,因為涉及到對processImports方法的疊代調用,請看下圖紅框旁邊的紅字說明:
    Spring注解[email protected]注解
    如上圖所示,第二步就是在processImports方法中調用了processImports方法,再次進入processImports之後,會着ImportSelector實作類傳回的bean名稱直接走到第三步的位置,第三步處理的就是沒有實作ImportSelector和ImportBeanDefinitionRegistrar這些接口的普通bean了;
  4. processImports方法對沒有實作ImportSelector和ImportBeanDefinitionRegistrar這些接口的普通bean的處理是執行processConfigurationClass方法,将這些bean放入了成員變量configurationClasses中,如下圖紅框所示:
    Spring注解[email protected]注解
  5. processImports方法分析完畢,Import注解導入的bean都被儲存在ConfigurationClassParser執行個體中,我們回到ConfigurationClassPostProcessor類的processConfigBeanDefinitions方法,如下圖,this.reader.loadBeanDefinitions(configClasses);負責處理processImports方法找出的那些打算通過@Import注解來注冊到spring容器的bean:
    Spring注解[email protected]注解
  6. 展開this.reader.loadBeanDefinitions(configClasses)方法,在ConfigurationClassBeanDefinitionReader類中,是對每個配置類逐個執行loadBeanDefinitionsForConfigurationClass方法:
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
        TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
        for (ConfigurationClass configClass : configurationModel) {
            loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
        }
    }
           
  1. 展開方法,真相大白,實作了ImportBeanDefinitionRegistrar接口的執行個體,會執行其registerBeanDefinitions方法,其餘普通的類,通過loadBeanDefinitionsFromImportedResources方法将其bean定義注冊在spring環境:
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
            TrackedConditionEvaluator trackedConditionEvaluator) {

        if (trackedConditionEvaluator.shouldSkip(configClass)) {
            String beanName = configClass.getBeanName();
            if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
                this.registry.removeBeanDefinition(beanName);
            }
            this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
            return;
        }

        if (configClass.isImported()) {
            registerBeanDefinitionForImportedConfigurationClass(configClass);
        }
        for (BeanMethod beanMethod : configClass.getBeanMethods()) {
            loadBeanDefinitionsForBeanMethod(beanMethod);
        }
        //普通的類,通過loadBeanDefinitionsFromImportedResources方法将其bean定義注冊在spring環境
        loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
        //實作了ImportBeanDefinitionRegistrar接口的執行個體,會在loadBeanDefinitionsFromRegistrars方法中執行其registerBeanDefinitions方法
        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }
           
  1. 前面将普通類、ImportBeanDefinitionRegistrar實作類、ImportSelector實作類的分析已經完成,對于DeferredImportSelector實作類的處理在processDeferredImportSelectors方法中,其實和ImportSelector實作類的處理并無差別,隻是處理時機比起ImportSelector實作類略晚,這裡就不多說了;

至此,通過Import注解注冊bean的四種方式已經全部分析完畢,小結如下:

  1. 普通類(即沒有實作ImportBeanDefinitionRegistrar、ImportSelector、DeferredImportSelector等接口的類)會通過ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources方法将bean定義注冊到spring容器;
  2. ImportSelector實作類,其selectImports方法傳回的bean的名稱,通過ConfigurationClassParser類的asSourceClass方法轉成SourceClass對象,然後被當作普通類處理;
  3. DeferredImportSelector實作類的處理和ImportSelector實作類的處理并無差別,隻是處理時機比起ImportSelector實作類略晚;
  4. ImportBeanDefinitionRegistrar實作類的registerBeanDefinitions方法會被調用,裡面可以注冊業務所需的bean定義;

官方API文檔中的疑問解答

在官方API文檔中,對ImportSelector接口的描述如下圖所示,紅框中的一段意思是: ImportSelector接口的實作類,如果同時也實作了EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware、ResourceLoaderAware這些接口中的一個或幾個,那麼這些接口對應的方法優先執行,然後才會執行ImportSelector接口的selectImports:

上圖紅框中的描述會讓我們不禁疑惑:spring是如何做到的呢?一起來看源碼吧:

  1. 再次打開ConfigurationClassParser類的processImports方法,如下圖兩個紅框所示,對于@Import注解值中的類,隻要實作了ImportBeanDefinitionRegistrar、ImportSelector、DeferredImportSelector等接口中的任何一個,都會調用invokeAwareMethods方法(如果實作的是ImportSelector或DeferredImportSelector接口,此時還沒有執行selectImports方法):
Spring注解[email protected]注解

2. 展開invokeAwareMethods方法,真相大白,這裡面檢查是否實作了EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware、ResourceLoaderAware等接口,如果實作了就調用對應的方法;

private void invokeAwareMethods(Object importStrategyBean) {
    if (importStrategyBean instanceof Aware) {
        if (importStrategyBean instanceof EnvironmentAware) {
            ((EnvironmentAware) importStrategyBean).setEnvironment(this.environment);
        }
        if (importStrategyBean instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader);
        }
        if (importStrategyBean instanceof BeanClassLoaderAware) {
            ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ?
                    ((ConfigurableBeanFactory) this.registry).getBeanClassLoader() :
                    this.resourceLoader.getClassLoader());
            ((BeanClassLoaderAware) importStrategyBean).setBeanClassLoader(classLoader);
        }
        if (importStrategyBean instanceof BeanFactoryAware && this.registry instanceof BeanFactory) {
            ((BeanFactoryAware) importStrategyBean).setBeanFactory((BeanFactory) this.registry);
        }
    }
}
           

至此,源碼分析工作已經結束,接下來實戰@Import注解的使用;

實戰通過Import注解動态建立bean執行個體

到了實戰驗證環節了,本次實戰的内容是建立一個springboot工程,通過@Import注解将bean注冊到spring容器

下面動手開發:

  1. 設計:customizeimport工程中,有四個接口(CustomizeService1,CustomizeService2,CustomizeService3,CustomizeService4,每個接口有個對應的實作類(CustomizeServiceImpl1、CustomizeServiceImpl2、CustomizeServiceImpl3、CustomizeServiceImpl4),在配置類通過@Import注解,用不同方式将這些實作類的執行個體注冊到spring容器中,在一個Controller中驗證這四個執行個體,執行個體的注冊到spring容器方式規劃如下表格所示:

    接口 實作類 注冊方式

    CustomizeService1 CustomizeService1Impl1 CustomizeService1Impl1.class作為Import注解的值

    CustomizeService2 CustomizeService1Impl2 作為ImportSelector實作類的selectImports方法的傳回值

    CustomizeService3 CustomizeService1Impl3 作為DeferredImportSelector實作類的selectImports方法的傳回值

    CustomizeService4 CustomizeService1Impl4 在ImportBeanDefinitionRegistrar實作類的registerBeanDefinitions方法中注冊

  2. 建立maven工程customizeimport,pom.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bolingcavalry</groupId>
    <artifactId>customizeimport</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>customizeimport</name>
    <description>Demo project for Spring Import annotation</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.15.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
           
  1. 建立接口實作類,除了類名不同,其他的都一樣,如下:
package com.bolingcavalry.customizeimport.service.impl;

import com.bolingcavalry.customizeimport.service.CustomizeService1;
import com.bolingcavalry.customizeimport.util.Utils;

public class CustomizeServiceImpl1 implements CustomizeService1 {

    public CustomizeServiceImpl1() {
        Utils.printTrack("construct : " + this.getClass().getSimpleName());
    }

    @Override

    public void execute() {
        System.out.println("execute : " + this.getClass().getSimpleName());
    }
}
           
  1. 建立ImportSelector接口的實作類CustomizeImportSelector,其selectImports方法傳回了CustomizeServiceImpl2的完整類名:
package com.bolingcavalry.customizeimport.selector;

import com.bolingcavalry.customizeimport.util.Utils;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata;

public class CustomizeImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        Utils.printTrack("selectImports : " + this.getClass().getSimpleName());
        return new String[]{"com.bolingcavalry.customizeimport.service.impl.CustomizeServiceImpl2"};
    }
}
           
  1. 建立DeferredImportSelector接口的實作類CustomizeDeferredImportSelector,其selectImports方法傳回了CustomizeServiceImpl3的完整類名:
package com.bolingcavalry.customizeimport.selector;

import com.bolingcavalry.customizeimport.util.Utils;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata;

public class CustomizeDeferredImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        Utils.printTrack("selectImports : " + this.getClass().getSimpleName());
        return new String[]{"com.bolingcavalry.customizeimport.service.impl.CustomizeServiceImpl3"};
    }
}
           
  1. 建立ImportBeanDefinitionRegistrar接口的實作類CustomizeImportBeanDefinitionRegistrar,其registerBeanDefinitions方法中在spring容器注冊了CustomizeServiceImpl4類的定義:
package com.bolingcavalry.customizeimport.registrar;

import com.bolingcavalry.customizeimport.service.impl.CustomizeServiceImpl4;
import com.bolingcavalry.customizeimport.util.Utils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class CustomizeImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    private final static String BEAN_NAME = "customizeService4";

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (!registry.containsBeanDefinition(BEAN_NAME)) {
            Utils.printTrack("start registerBeanDefinitions");
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(CustomizeServiceImpl4.class);
            beanDefinition.setSynthetic(true);
            registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
        }
    }
}
           
  1. 建立配置類SysConfig,使用了@Import注解:
package com.bolingcavalry.customizeimport;

import com.bolingcavalry.customizeimport.registrar.CustomizeImportBeanDefinitionRegistrar;
import com.bolingcavalry.customizeimport.selector.CustomizeDeferredImportSelector;
import com.bolingcavalry.customizeimport.selector.CustomizeImportSelector;
import com.bolingcavalry.customizeimport.service.impl.CustomizeServiceImpl1;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({CustomizeServiceImpl1.class,
        CustomizeImportSelector.class,
        CustomizeDeferredImportSelector.class,
        CustomizeImportBeanDefinitionRegistrar.class})
public class SysConfig {
}
           
  1. 建立Controller類HelloController ,用于驗證CustomizeService1等接口的執行個體是否可用:
package com.bolingcavalry.customizeimport.controller;

import com.bolingcavalry.customizeimport.service.CustomizeService1;
import com.bolingcavalry.customizeimport.service.CustomizeService2;
import com.bolingcavalry.customizeimport.service.CustomizeService3;
import com.bolingcavalry.customizeimport.service.CustomizeService4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @Autowired(required = false)
    CustomizeService1 customizeServiceImpl1;

    @Autowired(required = false)
    CustomizeService2 customizeServiceImpl2;

    @Autowired(required = false)
    CustomizeService3 customizeServiceImpl3;

    @Autowired(required = false)
    CustomizeService4 customizeServiceImpl4;

    @GetMapping("hello")
    public String hello(){
        customizeServiceImpl1.execute();
        customizeServiceImpl2.execute();
        customizeServiceImpl3.execute();
        customizeServiceImpl4.execute();
        return "finish";
    }
}
           
  1. 最後是啟動類CustomizeimportApplication:
package com.bolingcavalry.customizeimport;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CustomizeimportApplication {

    public static void main(String[] args) {
        SpringApplication.run(CustomizeimportApplication.class, args);
    }
}
           
  1. 啟動應用,觀察日志,可見Import的值都生效了,ImportSelector的selectImports被調用,ImportBeanDefinitionRegistrar的registerBeanDefinitions被調用,然後是CustomizeServiceImpl1 等類的執行個體被建立,篇幅所限就不在此将所有日志貼出,請您自行運作和檢查:
2018-09-10 12:36:33.917  INFO 5884 --- [           main] c.b.customizeimport.util.Utils           : selectImports : CustomizeImportSelector
************************************************************
java.lang.Thread.getStackTrace() 1,556 <- 
com.bolingcavalry.customizeimport.util.Utils.printTrack() 15 <- 
com.bolingcavalry.customizeimport.selector.CustomizeImportSelector.selectImports() 16 <- 
org.springframework.context.annotation.ConfigurationClassParser.processImports() 591 <- 
org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass() 304 <- 
org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass() 247 <- 
org.springframework.context.annotation.ConfigurationClassParser.parse() 192 <- 
org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass() 297 <- 
org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass() 247 <- 
org.springframework.context.annotation.ConfigurationClassParser.parse() 200 <- 
org.springframework.context.annotation.ConfigurationClassParser.parse() 169 <- 
org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions() 308 <- 
org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry() 228 <- 
org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors() 272 <- 
org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors() 92 <- 
org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors() 687 <- 
org.springframework.context.support.AbstractApplicationContext.refresh() 525 <- 
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh() 122 <- 
org.springframework.boot.SpringApplication.refresh() 693 <- 
org.springframework.boot.SpringApplication.refreshContext() 360 <- 
org.springframework.boot.SpringApplication.run() 303 <- 
org.springframework.boot.SpringApplication.run() 1,118 <- 
org.springframework.boot.SpringApplication.run() 1,107 <- 
com.bolingcavalry.customizeimport.CustomizeimportApplication.main() 10
************************************************************
2018-09-10 12:36:34.262  INFO 5884 --- [           main] c.b.customizeimport.util.Utils           : selectImports : CustomizeDeferredImportSelector
...
...
...
           
  1. 在浏覽器輸入:http://localhost:8080/hello,檢查控制台日志,發現CustomizeServiceImpl1等的execute方法都被順利執行,可見所有執行個體都在spring容器内:

execute : CustomizeServiceImpl1

execute : CustomizeServiceImpl2

execute : CustomizeServiceImpl3

execute : CustomizeServiceImpl4

繼續閱讀