Spring Boot Starter自動配置的加載原理

Spring Boot Starter的自動配置(Auto-configuration)是個很強大的東東,也是重要且巧妙的設計。不過不把它的原理搞清楚,在判斷哪些自動配置會被啓用時,會摸不着頭腦。下面就從源碼的角度來一探究竟。html

入門:自動配置的概念

Auto-configuration和Spring的Java Configuration不是一回事哦。具體可看Spring Boot文檔中的定義。java

Part 0:Spring Boot應用的啓動過程

參考文章bash

Part 1:引入spring.factories

從@SpringBootApplication開始

@SpringBootApplication註解的定義以下:ide

// 前面略
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
複製代碼

首先看@ComponentScan部分,使用了一個AutoConfigurationExcludeFilter做爲exclude filter,從字面上講,就是包掃描排除掉自動配置的包。So,若是Spring判斷某個類是自動配置類,則不會對其進行包掃描(以建立聲明的Bean)。具體邏輯再也不深究。spring-boot

這裏重點要講的是@EnableAutoConfiguration這個註解:post

// 省略不重要的內容
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
複製代碼

關注@Import(AutoConfigurationImportSelector.class)這句話。@Import咱們都很熟悉了,它是XML配置中的<import>的替代。然而AutoConfigurationImportSelector這個類並無被@Configuration修飾,不是個Java配置類。因此就很奇怪了。this

看源碼應該優先看註釋。lua

咱們來看看@Import的註釋透露了什麼:

Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes
複製代碼

嘿!原來它不只能導入Java配置類,還能處理ImportSelectorImportBeanDefinitionRegistrar

在講本節的重點ImportSelector以前,還須要提一下@AutoConfigurationPackage

/** * Indicates that the package containing the annotated class should be registered with * {@link AutoConfigurationPackages}. */
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
複製代碼

AutoConfigurationPackages.Registrar正好是個ImportBeanDefinitionRegistrar,它有個registerBeanDefinitions的接口方法,能夠直接註冊Bean。後面(擴展閱讀)能夠看到,ImportBeanDefinitionRegistrar是多個滴重要,它是各類"@EnableXX"註解的幕後英雄。

好了,再看看ImportSelector接口:

// 省略不重要註釋
/** * Interface that determine which @{@link Configuration} class(es) should be imported * based on a given selection criteria, usually one or more annotation attributes. */
public interface ImportSelector {
    /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}
複製代碼

因此,它是用來決定一個被@Configuration修飾了的Java配置類,應該被導入哪些配置的。依據是這個Java配置類的全部註解信息(保存在AnnotationMetadata裏面)。

好,下一步,咱們就來看看AutoConfigurationImportSelector如何實現。

AutoConfigurationImportSelector 類

此類對ImportSelector接口的實現以下:

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());
}
複製代碼

其中的getAutoConfigurationEntry方法又調用了getCandidateConfigurations方法,該方法以下:

/** * Return the auto-configuration class names that should be considered. By default * this method will load candidates using {@link SpringFactoriesLoader} */
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;
}
複製代碼

因此,到這裏,會從包的META-INF/spring.factories文件中讀取須要導入的自動配置類。更細節的代碼再也不往下貼了,具體可見SpringFactoriesLoader這個類。

接下來的問題是,selectImports是被誰調用的呢?說實話,調用的鏈路太深了,很容易被繞暈。下面簡單地描述一下主要流程。

Part 2:Spring的Java配置類加載過程

從ConfigurationClassPostProcessor提及

做爲一個Spring的PostProcessor,這個類的功能是處理有 @Configuration 修飾的Java配置類。關注其核心方法:

// 省略非關鍵代碼
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    ConfigurationClassParser parser = new ConfigurationClassParser(
    	this.metadataReaderFactory, this.problemReporter, this.environment,
    	this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    parser.parse(candidates);
    parser.validate();
    
    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    // 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);
}
複製代碼

關鍵邏輯就是調用ConfigurationClassParser類的parse方法,而後對解析到的全部ConfigurationClass,解析並加載內部定義的Bean。

ConfigurationClassParser#parse()

parse()方法主要是調用processConfigurationClass方法。

// 省略非核心代碼
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    	return;
    }
    
    // Recursively process the configuration class and its superclass hierarchy.
    SourceClass sourceClass = asSourceClass(configClass);
    do {
    	sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);
    
    this.configurationClasses.put(configClass, configClass);
}
複製代碼

此方法作了三件事:

  1. 判斷在PARSE_CONFIGURATION階段此Java Config類是否應該跳過(根據Conditional判斷)
  2. 調用doProcessConfigurationClass()作具體解析
  3. 將方法參數的配置類放入configurationClasses

而第二步的doProcessConfigurationClass()具體解析啥呢?其實就是遞歸地遍歷當前Java 配置類的各類@Import、內部類、聲明的@ComponentScan等等,當發現其它Java配置類時,再次調用parse()方法(或者processConfigurationClass()方法),解析那個類。最終的結果是把全部能找到的Java配置類都加入了configurationClasses容器中。

Part 3:再看@SpringBootApplication的加載流程

因爲@SpringBootApplication其實也就是一個帶有@Configuration的Java配置類,由Part II可知,它也會被按相同的方式解析。

而@SpringBootApplication所包含的註解最終又導入了AutoConfigurationImportSelector類,所以這個類將會被調用到。下面是調用到這個類的getCandidateConfigurations方法(Part I提到過)時的結果截圖:

spring.factories結果

到此,全部的Spring Boot Starter中定義的自動配置類都會被掃描到,並將被解析。

補充:@Conditional的實現

在Part 2 中,已經提到processConfigurationClass這個方法的第一行就是在判斷Conditioanal條件是否知足。當須要debug某個自動配置爲什麼生效/不生效時,能夠重點關注這裏。

總結:

@Import是用來導入配置類的,而導入方式主要分爲如下三種類型。

  1. 直接導入配置類,被@Configuration修飾的類。
  2. ImportSelector接口的實現類,返回一個配置類名稱的數組,而後再導入這些配置類。
  3. ImportBeanDefinitionRegistar接口的實現類,直接在接口方法中註冊Bean。

ImportSelector接口的一個實現類AutoConfigurationImportSelector則承包了從ClassPath下各個starter中的META-INF/spring.factories文件中讀取須要導入的自動配置類的工做。

@SpringBootApplication註解則間接繼承了AutoConfigurationImportSelector的功能。

在擴展閱讀中,你將能看到各類@Enable*註解的工做原理。

參考資料

相關文章
相關標籤/搜索