SpringBoot自動(dòng)配置原理

SpringBoot現(xiàn)在基本是標(biāo)配了晕翠,除非是老舊的項(xiàng)目盖文,或者保守一點(diǎn)的企業(yè),都會(huì)選擇SpringBoot双饥。

在SpringBoot出現(xiàn)之前媒抠,使用SSH或者SSM,都要很多的XML配置文件咏花,而這些配置趴生,在每個(gè)項(xiàng)目中阀趴,大部分都是相同的。

雖然都一樣苍匆,但項(xiàng)目都要配置刘急,可能會(huì)出現(xiàn)配置幾小時(shí),寫代碼幾分鐘的情況浸踩,把項(xiàng)目啟動(dòng)拖慢了叔汁。SpringBoot則是為了解決這種問題而生的,提高開發(fā)效率检碗。

用過SpringBoot的小伙伴都知道据块,在IDEA使用SpringBoot Initializer,快速配置項(xiàng)目折剃,寫一個(gè)Controller就可以另假,快速搭建起Web項(xiàng)目。

SpringBoot給我們提供了大量的starter怕犁,里面已經(jīng)幫我們配置了常用配置边篮,如果我們需要改動(dòng),則在application.yml中配置即可奏甫。

SpringBoot之所以可以這樣做苟耻,是因?yàn)樗脑O(shè)計(jì)策略,開箱即用和約定大于配置扶檐。

下面我們看下SpringBoot幫我們做了什么吧凶杖!

自動(dòng)裝配

要使用SpringBoot,我們需要指定parent父工程

基礎(chǔ)配置

pom文件指定parent父工程

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

點(diǎn)進(jìn)去會(huì)發(fā)現(xiàn)款筑,spring-boot-starter-parent也有父工程智蝠,就是spring-boot-dependencies,繼續(xù)點(diǎn)進(jìn)去

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>

spring-boot-dependencies看來是管理依賴和版本號(hào)的奈梳,所以我們依賴第三方庫(kù)時(shí)杈湾,如果在這個(gè)依賴列表中有,則不需要寫版本號(hào)了

<properties>
    <activemq.version>5.15.10</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.76</appengine-sdk.version>
    <artemis.version>2.10.1</artemis.version>
    <aspectj.version>1.9.4</aspectj.version>
    <assertj.version>3.13.2</assertj.version>
    <atomikos.version>4.0.6</atomikos.version>
    <awaitility.version>4.0.1</awaitility.version>
    <bitronix.version>2.1.4</bitronix.version>
    <build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
    <byte-buddy.version>1.10.1</byte-buddy.version>
    ...太多了攘须,省略其他
</properties>

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot</artifactId>
        <version>2.2.0.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
        <version>2.2.0.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test-autoconfigure</artifactId>
        <version>2.2.0.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-actuator</artifactId>
        <version>2.2.0.RELEASE</version>
      </dependency>
    <dependencies>
    ...太多了漆撞,省略其他
<dependencyManagement>

我們的父工程spring-boot-starter-parent,還幫我們指定了配置文件的格式

<build>
    <resources>
      <resource>
        <filtering>true</filtering>
        <directory>${basedir}/src/main/resources</directory>
        <!-- 指定了配置文件的格式于宙,加載順序?yàn)閥ml => yaml => properties -->
        <includes>
          <include>**/application*.yml</include>
          <include>**/application*.yaml</include>
          <include>**/application*.properties</include>
        </includes>
      </resource>
      <resource>
        <directory>${basedir}/src/main/resources</directory>
        <excludes>
          <exclude>**/application*.yml</exclude>
          <exclude>**/application*.yaml</exclude>
          <exclude>**/application*.properties</exclude>
        </excludes>
      </resource>
    </resources>
<build>

啟動(dòng)器 starter

SpringBoot將每種使用場(chǎng)景所需要的依賴和依賴浮驳,封裝成一個(gè)啟動(dòng)器starter,我們需要引入某種領(lǐng)域的功能時(shí)捞魁,直接依賴對(duì)應(yīng)的starer即可至会。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

例如我們常用的Web開發(fā),需要依賴SpringMVC等谱俭,SpringBoot提供了spring-boot-starter-web啟動(dòng)器

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

我們點(diǎn)進(jìn)行該starter奉件,他給我們定義了以下依賴:

  1. spring-boot-starter SpringBoot基礎(chǔ)啟動(dòng)器
  2. spring-boot-starter-json json序列化宵蛀、反序列化的啟動(dòng)器
  3. spring-boot-starter-tomcat 內(nèi)嵌Tomcat
  4. spring-web和spring-webmvc,就是我們的SpringMVC
<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
      <exclusions>
        <exclusion>
          <artifactId>tomcat-embed-el</artifactId>
          <groupId>org.apache.tomcat.embed</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>
</dependencies>

啟動(dòng)類 SpringBootApplication

SpringBoot要求我們提供一個(gè)啟動(dòng)類县貌,并且類頭加上 @SpringBootApplication注解术陶,該注解就是SpringBoot啟動(dòng)的核心。

@SpringBootApplication
public class SpringbootEsApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootEsApplication.class, args);
        log.info("項(xiàng)目啟動(dòng)成功煤痕,訪問地址:http://localhost:8081/");
    }
}

我們點(diǎn)進(jìn)去@SpringBootApplication注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
    //省略屬性...
}

我們會(huì)發(fā)現(xiàn)SpringBootApplication是一個(gè)復(fù)合注解瞳别,當(dāng)中最重要的是@SpringBootConfiguration@EnableAutoConfiguration,這2個(gè)注解杭攻。
@ComponentScan注解是包掃描祟敛,因?yàn)闆]有配置掃描包,默認(rèn)是掃描標(biāo)識(shí)該注解的類的包兆解,以及它以下的子包馆铁,所以啟動(dòng)類一般在根包下。

  • @SpringBootConfiguration注解

我們發(fā)現(xiàn)@SpringBootConfiguration注解 锅睛,最主要是加上了@Configuration注解埠巨。
我們知道@Configuration注解 就代表了一個(gè)JavaConfig方式的Spring的容器,所以我們啟動(dòng)器類也相當(dāng)于一個(gè)容器现拒。

SpringBootConfiguration注解沒什么可看了辣垒,我們看下一個(gè)注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(
    proxyBeanMethods = false
)
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
  • @EnableAutoConfiguration注解

@EnableAutoConfiguration注解中,主要注解是@Import(AutoConfigurationImportSelector.class)印蔬。
@Import注解勋桶,幫我們導(dǎo)入了AutoConfigurationImportSelector這個(gè)類

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class<?>[] exclude() default {};

String[] excludeName() default {};
}
  • AutoConfigurationImportSelector類

AutoConfigurationImportSelector類實(shí)現(xiàn)了DeferredImportSelector接口,該接口繼承ImportSelector接口 侥猬,會(huì)要求復(fù)寫selectImports()方法例驹。

ImportSelector接口,主要是為了導(dǎo)入@Configuration配置的退唠,而DeferredImportSelector是延期導(dǎo)入鹃锈,當(dāng)所有的@Configuration都處理完成后,再調(diào)用DeferredImportSelector進(jìn)行處理瞧预。

所以AutoConfigurationImportSelector類是延遲導(dǎo)入的屎债,所有@Configuration都處理完后,再調(diào)用它的selectImports()方法垢油。

selectImports()方法盆驹,調(diào)用了getAutoConfigurationEntry()方法,而getAutoConfigurationEntry()又調(diào)用了getCandidateConfigurations()方法秸苗。
getCandidateConfigurations()方法是重點(diǎn)召娜!

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
}
  • getCandidateConfigurations()方法

方法中运褪,調(diào)用SpringFactoriesLoader.loadFactoryNames()惊楼,傳入2個(gè)參數(shù)玖瘸,EnableAutoConfiguration的Class和Bean的ClassLoader。

loadFactoryNames()方法檀咙,返回一個(gè)集合雅倒,如果集合為空,進(jìn)入下一句的Assert斷言弧可,就會(huì)拋出異常蔑匣。

最后返回這個(gè)配置集合。

//AutoConfigurationImportSelector
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
            getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
            + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

protected ClassLoader getBeanClassLoader() {
    return this.beanClassLoader;
}
  • SpringFactoriesLoader

loadFactoryNames()方法棕诵,獲取了傳入的EnableAutoConfiguration注解的Class裁良,調(diào)用loadSpringFactories()方法。
loadSpringFactories()方法校套,會(huì)讀取jar包中META-INF目錄的spring.factories配置文件价脾。

如果讀取不到,則返回一個(gè)空集合笛匙。

public final class SpringFactoriesLoader {
    //jar包中的META-INF目錄下侨把,spring.factories配置文件
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }
    
        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
}
  • spring.factories配置文件

我們選一個(gè)starter,例如spring-boot-autoconfigure妹孙,找到它的META-INF目錄秋柄,找到spring.factories文件,打開蠢正。

我們發(fā)現(xiàn)文件里骇笔,配置了很多自動(dòng)配置屬性(內(nèi)容有刪減,實(shí)在太多了O浮)蜘拉。
它的形式是Key-Value,例如其中一個(gè)Key是EnableAutoConfiguration的全類名有鹿,它的Value是好幾個(gè)名字以AutoConfiguration結(jié)尾的類旭旭,每個(gè)類之間用逗號(hào)分隔。

剛才我們跟蹤的loadFactoryNames()方法葱跋,傳入的EnableAutoConfiguration的Class持寄,就是要從spring.factories配置文件中找到它對(duì)應(yīng)的那一組Value。

我們以ServletWebServerFactoryAutoConfiguration為例娱俺,點(diǎn)進(jìn)去看一下

# 省略其他配置...

# Auto Configure !!!!!!!! 重點(diǎn)在這里 !!!!!!!!
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

# 省略其他配置...
  • ServletWebServerFactoryAutoConfiguration類

我們看到該類的類頭上稍味,有@EnableConfigurationProperties注解,該屬性表示加載配置屬性荠卷,這里指定了一個(gè)ServerProperties類模庐。

我們點(diǎn)進(jìn)去ServerProperties類看一下

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    //...省略
}
  • ServerProperties類

這個(gè)是一個(gè)和配置信息相對(duì)應(yīng)的類,它類頭上配置了@ConfigurationProperties注解油宜,它可以將配置文件中的配置項(xiàng)的內(nèi)容掂碱,映射到我們的類的變量上怜姿。

注解上,配置的prefix屬性疼燥,就代表了server.xxx系列配置沧卢,例如我們配置端口:server.port,該注解將我們的配置映射到ServerProperties上醉者。

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
    /**
     * Server HTTP port.
     */
    private Integer port;

    /**
     * Network address to which the server should bind.
     */
    private InetAddress address;
}

到此為止但狭,自動(dòng)配置的流程基本通了,總結(jié)一下:

SpringBoot啟動(dòng)類的main方法啟動(dòng)時(shí)撬即,會(huì)找@EnableAutoConfiguration注解立磁,而該注解就在@SpringBootApplication上。而@EnableAutoConfiguration注解上剥槐,使用了@Import注解息罗,導(dǎo)入了AutoConfigurationImportSelector類。
而該類才沧,會(huì)去找META-INF/spring.factories配置文件迈喉,這個(gè)配置文件中配置了一系列的以AutoConfiguration結(jié)尾的類,就是自動(dòng)配置類温圆。
而每個(gè)配置類挨摸,都有一個(gè)Properties結(jié)尾的配置類,它和我們?cè)趛ml中的配置項(xiàng)時(shí)一一對(duì)應(yīng)的岁歉,相當(dāng)于綁定配置到了該對(duì)象中得运。

如果只是想面試了解一下,到這里就可以了锅移,而如果更想深入熔掺,就要繼續(xù)跟一下。

如果要繼續(xù)跟非剃,就還有一個(gè)疑點(diǎn)置逻,自動(dòng)裝配是什么時(shí)候開始的呢,其實(shí)就是AutoConfigurationImportSelector類上的selectImports()方法备绽,還不知道它什么會(huì)被調(diào)用券坞。

何時(shí)開始進(jìn)行自動(dòng)裝配

我們回歸到Spring,Spring應(yīng)用啟動(dòng)肺素,會(huì)在AbstractApplicationContext類中恨锚,調(diào)用refresh()方法。

refresh()方法中倍靡,調(diào)用了invokeBeanFactoryPostProcessors()方法猴伶,該方法是用來處理BeanFactoryPostProcessor接口的,而BeanFactoryPostProcessor的有一個(gè)子接口BeanDefinitionRegistryPostProcessor

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        //省略無關(guān)代碼...

        invokeBeanFactoryPostProcessors(beanFactory);

        //省略無關(guān)代碼...
    }
}

//BeanDefinitionRegistryPostProcessor
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
  • ConfigurationClassPostProcessor類

子接口BeanDefinitionRegistryPostProcessor他挎,有一個(gè)實(shí)現(xiàn)類ConfigurationClassPostProcessor筝尾,它是專門處理@Configuration注解的。

processConfigBeanDefinitions()方法中雇盖,就是處理@Configuration注解的類忿等。主要是使用ConfigurationClassParser類的parse()方法栖忠。

我們進(jìn)去parse()方法崔挖,看一下

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
        PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

    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 {
            //解析處理@Configuration注解的類
            parser.parse(candidates);
            parser.validate();

            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);

            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);

            candidates.clear();
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        while (!candidates.isEmpty());
    }
}
  • ConfigurationClassParser類的parse()方法

首先類中有一個(gè)內(nèi)部類DeferredImportSelectorHandler,構(gòu)造方法ConfigurationClassParser實(shí)例時(shí)庵寞,就創(chuàng)建該內(nèi)部類的實(shí)例狸相。

parse()方法調(diào)用時(shí),最后一句調(diào)用了processDeferredImportSelectors()方法捐川。

class ConfigurationClassParser {
    public void parse(Set<BeanDefinitionHolder> configCandidates) {
            this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                    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 (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
            }
        }
        //重點(diǎn)
        processDeferredImportSelectors();
    }
}
  • processDeferredImportSelectors()方法

重點(diǎn)在String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());脓鹃。

調(diào)用的是DeferredImportSelectorHolder類,它保存了DeferredImportSelector的引用古沥,在這個(gè)for循環(huán)中瘸右,調(diào)用了DeferredImportSelectorselectImports()方法,從而調(diào)用到了我們之前分析的AutoConfigurationImportSelector類中的selectImports()方法了岩齿。

private void processDeferredImportSelectors() {
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    this.deferredImportSelectors = null;
    Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);

    for (DeferredImportSelectorHolder deferredImport : deferredImports) {
        ConfigurationClass configClass = deferredImport.getConfigurationClass();
        try {
            String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
            processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to process import candidates for configuration class [" +
                    configClass.getMetadata().getClassName() + "]", ex);
        }
    }
}

//該類太颤,保存了配置類和DeferredImportSelector的引用
private static class DeferredImportSelectorHolder {
    private final ConfigurationClass configurationClass;

    private final DeferredImportSelector importSelector;

    public DeferredImportSelectorHolder(ConfigurationClass configClass, DeferredImportSelector selector) {
        this.configurationClass = configClass;
        this.importSelector = selector;
    }

    public ConfigurationClass getConfigurationClass() {
        return this.configurationClass;
    }

    public DeferredImportSelector getImportSelector() {
        return this.importSelector;
    }
}

參考資料

SpringBoot:認(rèn)認(rèn)真真梳理一遍自動(dòng)裝配原理

Spring Boot面試殺手锏 — 自動(dòng)配置原理

深入理解SpringBoot之自動(dòng)裝配

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盹沈,隨后出現(xiàn)的幾起案子龄章,更是在濱河造成了極大的恐慌,老刑警劉巖乞封,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件做裙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肃晚,警方通過查閱死者的電腦和手機(jī)锚贱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來关串,“玉大人惋鸥,你說我怎么就攤上這事『凡” “怎么了卦绣?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)飞蚓。 經(jīng)常有香客問我滤港,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任溅漾,我火速辦了婚禮山叮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘添履。我一直安慰自己屁倔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布暮胧。 她就那樣靜靜地躺著锐借,像睡著了一般。 火紅的嫁衣襯著肌膚如雪往衷。 梳的紋絲不亂的頭發(fā)上钞翔,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音席舍,去河邊找鬼布轿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛来颤,可吹牛的內(nèi)容都是我干的汰扭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼福铅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼萝毛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起本讥,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤珊泳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拷沸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體色查,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年撞芍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秧了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡序无,死狀恐怖验毡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帝嗡,我是刑警寧澤晶通,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站哟玷,受9級(jí)特大地震影響狮辽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一喉脖、第九天 我趴在偏房一處隱蔽的房頂上張望椰苟。 院中可真熱鬧,春花似錦树叽、人聲如沸舆蝴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洁仗。三九已至,卻和暖如春仇轻,著一層夾襖步出監(jiān)牢的瞬間京痢,已是汗流浹背奶甘。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工篷店, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人臭家。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓疲陕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親钉赁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蹄殃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355