SpringBoot 啟動類的原理

程式猿進階 發表於 2020-10-30

目錄

@SpringBootApplication

@SpringBootConfiguration

@ComponentScan

@EnableAutoConfiguration

@AutoConfigurationPackage

Registrar

AutoConfigurationImportSelector

selectImports


SpringBoot啟動類上使用 @SpringBootApplication註解,該註解是一個組合註解,包含多個其它註解。

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

@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}
)}
)
public @interface SpringBootApplication {

@SpringBootApplication 註解上標有三個註解@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan。其它四個註解用來宣告 SpringBootAppliction 為一個註解 [連結]

@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

@SpringBootConfiguration 註解中沒有定義任何屬性資訊,而該註解上有一個註解 @Configuration,用於標識配置類。所以 @SpringBootConfiguration註解的功能和 @Configuration註解的功能相同,用於標識配置類,與 @Bean搭配使用,一個帶有 @Bean的註解方法將返回一個物件,該物件應該被註冊為在 Spring應用程式上下文中的 bean。如下案例:

@Configuration 
public class Conf { 
      @Bean 
      public Car car() { 
          return new Car(); 
      }
}

@ComponentScan

@ComponentScan 這個註解在 Spring中很重要,它對應 XML配置中的元素,@ComponentScan的功能其實就是自動掃描並載入符合條件的元件(比如 @Component和 @Repository等)或者 bean定義,最終將這些 bean定義載入到 IoC容器中。我們可以通過 basePackages等屬性來細粒度的定製 @ComponentScan自動掃描的範圍,如果不指定,則預設 Spring框架實現會從宣告 @ComponentScan所在類的 package進行掃描。注:所以 SpringBoot的啟動類最好是放在root package下,因為預設不指定basePackages。

@EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {

個人感覺 @EnableAutoConfiguration這個 Annotation最為重要,Spring框架提供的各種名字為@Enable開頭的Annotation,藉助 @Import的支援,收集和註冊特定場景相關的 bean定義。@EnableAutoConfiguration也是藉助 @Import的幫助,將所有符合自動配置條件的 bean定義載入到IoC容器。@EnableAutoConfiguration註解上標註了兩個註解,@AutoConfigurationPackage@Import。@Import 註解在 SpringIOC一些註解的原始碼中比較常見,主要用來給容器匯入目標bean。這裡 @Import註解給容器匯入的元件用於自動配置:AutoConfigurationImportSelector ; 而 @AutoConfigurationPackage註解是Spring自定義的註解,用於掃描啟動類所在的包及其子包下的自定義類。

@AutoConfigurationPackage

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
}

@AutoConfigurationPackage 註解上的 @Import註解,給容器匯入了 Registrar元件

Registrar

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
	Registrar() {
	}

	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
	}

	public Set<Object> determineImports(AnnotationMetadata metadata) {
		return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
	}
}

Registrar是抽象類 AutoConfigurationPackages的內部靜態類,Registrar內的 registerBeanDefinitions()方法負責將註解所在的包及其子包下的所有元件註冊進容器。這也是為什麼 SpringBoot的啟動類要在其他類的父包或在同一個包中。

AutoConfigurationImportSelector

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

藉助AutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助 SpringBoot應用將所有符合條件的 @Configuration配置都載入到當前 SpringBoot建立並使用的 IoC容器。藉助於 Spring框架原有的一個工具類:SpringFactoriesLoader的支援,@EnableAutoConfiguration可以智慧的自動配置功效才得以大功告成!

SpringFactoriesLoader屬於 Spring框架私有的一種擴充套件方案,其主要功能就是從指定的配置檔案 META-INF/spring.factories 載入配置。

public abstract class SpringFactoriesLoader {
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        ......
    }
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ......
    }
}

配合 @EnableAutoConfiguration使用的話,它更多是提供一種配置查詢的功能支援,即根據 @EnableAutoConfiguration的完整類名 org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查詢的Key,獲取對應的一組 @Configuration類。
SpringBoot 啟動類的原理 

上圖就是從 SpringBoot的 autoconfigure依賴包中的 META-INF/spring.factories配置檔案中摘錄的一段內容,可以很好地說明問題。

所以,@EnableAutoConfiguration自動配置的魔法騎士就變成了:從 classpath中搜尋所有的 META-INF/spring.factories配置檔案,並將其中 org.springframework.boot.autoconfigure.EnableutoConfiguration對應的配置項通過反射(Java Refletion)例項化為對應的標註了 @Configuration的 JavaConfig形式的IoC容器配置類,然後彙總為一個並載入到 IoC容器。

AutoConfigurationImportSelector 類實現類很多 Aware介面,而 Aware介面的功能是使用一些 Spring內建的例項獲取一些想要的資訊,如容器資訊、環境資訊、容器中註冊的 bean資訊等。而 AutoConfigurationImportSelector 類的作用是將 Spring中已經定義好的自動配置類注入容器中,而實現該功能的方法是 selectImports方法:

selectImports

註冊 Spring中定義好的配置類

public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!this.isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	} else {
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
		AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
}