Spring Boot 源碼分析 - 剖析 @SpringBootApplication 注解


參考 知識星球芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄

該系列文章是筆者在學習 Spring Boot 過程中總結下來的,里面涉及到相關源碼,可能對讀者不太友好,請結合我的源碼注釋 Spring Boot 源碼分析 GitHub 地址 進行閱讀

Spring Boot 版本:2.2.x

最好對 Spring 源碼有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章導讀》 系列文章

如果該篇內容對您有幫助,麻煩點擊一下“推薦”,也可以關注博主,感激不盡~

該系列其他文章請查看:《精盡 Spring Boot 源碼分析 - 文章導讀》

概述

現如今,Spring Boot 在許多中大型企業中被普及,想必大家對於 @SpringBootApplication 並不陌生,這個注解通常標注在我們應用的啟動類上面,標記是一個 Spring Boot 應用,同時開啟自動配置的功能,那么你是否有深入了解過該注解呢?沒有的話,或許這篇文章可以讓你對它有一個新的認識。

提示:@EnableAutoConfiguration 是開啟自動配置功能的模塊驅動注解,是 Spring Boot 的核心注解

整篇文章主要是對這個注解,也就是 Spring Boot 的自動配置功能進行展述

@SpringBootApplication

org.springframework.boot.autoconfigure.SpringBootApplication 注解在 Spring Boot 的 spring-boot-autoconfigre 子模塊下,當我們引入 spring-boot-starter 模塊后會自動引入該子模塊

該注解是一個組合注解,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 表明該注解定義在某個類上時,其子類會繼承該注解
@SpringBootConfiguration // 繼承 `@Configuration` 注解
@EnableAutoConfiguration // 開啟自動配置功能
// 掃描指定路徑下的 Bean
@ComponentScan( excludeFilters = {
    			// 默認沒有 TypeExcludeFilter
				@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    			// 排除掉自動配置類
				@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	/**
	 * 需要自動配置的 Class 類
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * 需要自動配置的類名稱
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	/**
	 * 需要掃描的路徑
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * 需要掃描的 Class 類
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	/**
	 * 被標記的 Bean 是否進行 CGLIB 提升
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

@SpringBootApplication 注解就是一個組合注解,里面的每個配置都是元注解中對應的屬性,上面已做描述

該注解上面的 @Inherited 元注解是 Java 提供的,標注后表示當前注解定義在某個類上時,其子類會繼承該注解,我們一起來看看其他三個注解

@SpringBootConfiguration

org.springframework.boot.SpringBootConfiguration 注解,Spring Boot 自定義注解

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

    /**
	 * 被標記的 Bean 是否進行 CGLIB 提升
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

該注解很簡單,上面標注了 @Configuration 元注解,所以作用相同,同樣是將一個類標注為配置類,能夠作為一個 Bean 被 Spring IoC 容器管理

至於為什么不直接使用 @Configuration 注解呢,我想這應該是 領域驅動設計 中的一種思想,可以使得 Spring Boot 更加靈活,總有它的用武之地

領域驅動設計:Domain-Driven Design,簡稱 DDD。過去系統分析和系統設計都是分離的,這樣割裂的結果導致需求分析的結果無法直接進行設計編程,而能夠進行編程運行的代碼卻扭曲需求,導致客戶運行軟件后才發現很多功能不是自己想要的,而且軟件不能快速跟隨需求變化。DDD 則打破了這種隔閡,提出了領域模型概念,統一了分析和設計編程,使得軟件能夠更靈活快速跟隨需求變化。

@ComponentScan

org.springframework.context.annotation.ComponentScan 注解,Spring 注解,掃描指定路徑下的標有 @Component 注解的類,解析成 Bean 被 Spring IoC 容器管理

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

    /**
     * 指定的掃描的路徑
     */
	@AliasFor("basePackages")
	String[] value() default {};

    /**
     * 指定的掃描的路徑
     */
	@AliasFor("value")
	String[] basePackages() default {};

    /**
     * 指定的掃描的 Class 對象
     */
	Class<?>[] basePackageClasses() default {};

	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	boolean useDefaultFilters() default true;

    /**
     * 掃描時的包含過濾器
     */
	Filter[] includeFilters() default {};
	/**
     * 掃描時的排除過濾器
     */
	Filter[] excludeFilters() default {};

	boolean lazyInit() default false;
}

想深入了解該注解的小伙伴可以查看我前面對 Spring IoC 進行源碼分析的文章:

該注解通常需要和 @Configuration 注解一起使用,因為需要先被當做一個配置類,然后解析到上面有 @ComponentScan 注解后則處理該注解,通過 ClassPathBeanDefinitionScanner 掃描器去掃描指定路徑下標注了 @Component 注解的類,將他們解析成 BeanDefinition(Bean 的前身),后續則會生成對應的 Bean 被 Spring IoC 容器管理

當然,如果該注解沒有通過 basePackages 指定路徑,Spring 會選在以該注解標注的類所在的包作為基礎路徑,然后掃描包下面的這些類

@EnableAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration 注解,Spring Boot 自定義注解,用於驅動 Spring Boot 自動配置模塊

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 注冊一個 Bean 保存當前注解標注的類所在包路徑
@Import(AutoConfigurationImportSelector.class) // Spring Boot 自動配置的實現
public @interface EnableAutoConfiguration {
	/**
	 * 可通過這個配置關閉 Spring Boot 的自動配置功能
	 */
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * 指定需要排除的自動配置類的 Class 對象
	 */
	Class<?>[] exclude() default {};

	/**
	 * 指定需要排除的自動配置類的名稱
	 */
	String[] excludeName() default {};
}

對於 Spring 中的模塊驅動注解的實現都是通過 @Import 注解來實現的

模塊驅動注解通常需要結合 @Configuration 注解一起使用,因為需要先被當做一個配置類,然后解析到上面有 @Import 注解后則進行處理,對於 @Import 注解的值有三種情況:

  1. 該 Class 對象實現了 ImportSelector 接口,調用它的 selectImports(..) 方法獲取需要被處理的 Class 對象的名稱,也就是可以將它們作為一個 Bean 被 Spring IoC 管理

    • 該 Class 對象實現了 DeferredImportSelector 接口,和上者的執行時機不同,在所有配置類處理完后再執行,且支持 @Order 排序
  2. 該 Class 對象實現了 ImportBeanDefinitionRegistrar 接口,會調用它的 registerBeanDefinitions(..) 方法,自定義地往 BeanDefinitionRegistry 注冊中心注冊 BeanDefinition(Bean 的前身)

  3. 該 Class 對象是一個 @Configuration 配置類,會將這個類作為一個 Bean 被 Spring IoC 管理

對於 @Import 注解不熟悉的小伙伴可查看我前面的 《死磕Spring之IoC篇 - @Bean 等注解的實現原理》 這篇文章

這里的 @EnableAutoConfiguration 自動配置模塊驅動注解,通過 @Import 導入 AutoConfigurationImportSelector 這個類(實現了 DeferredImportSelector 接口)來驅動 Spring Boot 的自動配置模塊,下面會進行分析

@AutoConfigurationPackage

我們注意到 @EnableAutoConfiguration 注解上面還有一個 @AutoConfigurationPackage 元注解,它的作用就是注冊一個 Bean,保存了當前注解標注的類所在包路徑

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/**
 * 將當前注解所標注的類所在包名封裝成一個 {@link org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages} 進行注冊
 * 例如 JPA 模塊的會使用到這個對象(JPA entity scanner)
 */
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage { }

同樣這里使用了 @Import 注解來實現的,對應的是一個 AutoConfigurationPackages.Registrar 內部類,如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 注冊一個 BasePackages 類的 BeanDefinition,角色為內部角色,名稱為 `org.springframework.boot.autoconfigure.AutoConfigurationPackages`
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        // 將注解元信息封裝成 PackageImport 對象,對注解所在的包名進行封裝
        return Collections.singleton(new PackageImport(metadata));
    }
}

比較簡單,這里直接跳過了

自動配置

在開始之前,我們先來了解一下 Spring Boot 的自動配置,就是通過引入某個功能的相關 jar 包依賴后,Spring Boot 能夠自動配置應用程序,讓我們很方便的使用該功能

  • 例如當你引入 spring-boot-starter-aop 后,會自動引入 AOP 相關的 jar 包依賴,那么在 spring-boot-autoconfigure 中有一個 AopAutoConfiguration 自動配置類會自動驅動整個 AOP 模塊

  • 例如當你引入 spring-boot-starter-web 后,會自動引入 Spring MVC、Tomcat 相關的 jar 包依賴,那么在 spring-boot-autoconfigure 中會有相應的自動配置類會自動配置 Spring MVC

當然,還有許多自動配置類,結合這 Spring Boot 的 Starter 模塊,讓許多功能或者第三方 jar 包能夠很簡便的和 Spring Boot 整合在一起使用

現在很多開源框架都提供了對應的 Spring Boot Starter 模塊,能夠更好的整合 Spring Boot,當你熟悉自動配置功能后,你也可以很輕松的寫一個 Starter 包供他人使用😃😃😃

這里先提前劇透一下,自動配置類為什么在你引入相關 jar 包后會自動配置對應的模塊呢?

主要就是拓展了 Spring 的 Condition,例如 @ConditionalOnClass 注解,當存在指定的 Class 對象時才注入某個 Bean

同時也可以再結合 @EnableXxx 模塊注解,通過 @Import 注解驅動某個模塊

具體細節,請繼續往下看

AutoConfigurationImportSelector

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector,實現了 DeferredImportSelector 接口,是 @EnableAutoConfiguration 注解驅動自動配置模塊的核心類

直接看到實現的 ImportSelector 接口的方法

1. selectImports 方法

selectImports(AnnotationMetadata) 方法,返回需要注入的 Bean 的類名稱

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // <1> 如果通過 `spring.boot.enableautoconfiguration` 配置關閉了自動配置功能
    if (!isEnabled(annotationMetadata)) {
        // 返回一個空數組
        return NO_IMPORTS;
    }
    /**
     * <2> 解析 `META-INF/spring-autoconfigure-metadata.properties` 文件,生成一個 AutoConfigurationMetadata 自動配置類元數據對象
     *
     * 說明:引入 `spring-boot-autoconfigure-processor` 工具模塊依賴后,其中會通過 Java SPI 機制引入 {@link AutoConfigureAnnotationProcessor} 注解處理器在編譯階段進行相關處理
     * 其中 `spring-boot-autoconfigure` 模塊會引入該工具模塊(不具有傳遞性),那么 Spring Boot 在編譯 `spring-boot-autoconfigure` 這個 `jar` 包的時候,
     * 在編譯階段會掃描到帶有 `@ConditionalOnClass` 等注解的 `.class` 文件,也就是自動配置類,將自動配置類的信息保存至 `META-INF/spring-autoconfigure-metadata.properties` 文件中
     * 例如保存類 `自動配置類類名.注解簡稱` => `注解中的值(逗號分隔)` 和 `自動配置類類名` => `空字符串`
     *
     * 當然,你自己寫的 Spring Boot Starter 中的自動配置模塊也可以引入這個 Spring Boot 提供的插件
     */
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    // <3> 從所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解對應的類(需要自動配置的類)
    // 會進行過濾處理,然后封裝在一個對象中
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
    // <4> 返回所有需要自動配置的類
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

過程如下:

  1. 如果通過 spring.boot.enableautoconfiguration 配置關閉了自動配置功能,那么直接返回一個空數組
  2. 解析 META-INF/spring-autoconfigure-metadata.properties 文件,生成一個 AutoConfigurationMetadata 自動配置類元數據對象
  3. 調用 getAutoConfigurationEntry(..) 方法, 從所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解對應的類(需要自動配置的類),會進行過濾處理,然后封裝在一個 AutoConfigurationEntry 對象中
  4. 返回所有需要自動配置的類

上面第 2 步調用的方法:

final class AutoConfigurationMetadataLoader {

	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			// <1> 獲取所有 `META-INF/spring-autoconfigure-metadata.properties` 文件 URL
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();
			// <2> 加載這些文件並將他們的屬性添加到 Properties 中
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			// <3> 將這個 Properties 封裝到 PropertiesAutoConfigurationMetadata 對象中並返回
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

	static AutoConfigurationMetadata loadMetadata(Properties properties) {
		return new PropertiesAutoConfigurationMetadata(properties);
	}
}

這一步困惑了我很久,因為在 Spring Boot 工程中根本找不到 META-INF/spring-autoconfigure-metadata.properties 文件,而我們自己也沒有配置過,但是在我們自己的 Spring Boot 應用依賴的 spring-boot-autoconfigure.jar 包里面又存在這個文件,如下:

那么這是為什么呢?經過我長時間的 Google,找到了答案

  1. 在引入 spring-boot-autoconfigure-processor 工具模塊依賴后,其中會通過 Java SPI 機制引入 AutoConfigureAnnotationProcessor 注解處理器在編譯階段進行相關處理

  2. 其中 spring-boot-autoconfigure 模塊會引入該工具模塊(不具有傳遞性),那么 Spring Boot 在編譯 spring-boot-autoconfigure 這個 jar 包的時候,在編譯階段會掃描到帶有 @ConditionalOnClass 等注解的 .class 文件,也就是自動配置類,然后將自動配置類的一些信息保存至 META-INF/spring-autoconfigure-metadata.properties 文件中

  3. 文件中保存了 自動配置類類名.注解簡稱 --> 注解中的值(逗號分隔)自動配置類類名 --> 空字符串

  4. 當然,你自己寫的 Spring Boot Starter 中的自動配置模塊也可以引入這個 Spring Boot 提供的插件

得到的結論:

至於為什么這么做,是因為 Spring Boot 提供的自動配置類比較多,而我們不可能使用到很多自動配置功能,大部分都沒必要,如果每次你啟動應用的過程中,都需要一個一個去解析他們上面的 Conditional 注解,那么肯定會有不少的性能損耗

這里,Spring Boot 做了一個優化,通過自己提供的工具,在編譯階段將自動配置類的一些注解信息保存在一個 properties 文件中,這樣一來,在你啟動應用的過程中,就可以直接讀取該文件中的信息,提前過濾掉一些自動配置類,相比於每次都去解析它們所有的注解,性能提升不少

2. getAutoConfigurationEntry 方法

getAutoConfigurationEntry(AutoConfigurationMetadata, AnnotationMetadata) 方法,返回符合條件的自動配置類,如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    // <1> 如果通過 `spring.boot.enableautoconfiguration` 配置關閉了自動配置功能
    if (!isEnabled(annotationMetadata)) {
        // 則返回一個“空”的對象
        return EMPTY_ENTRY;
    }
    // <2> 獲取 `@EnableAutoConfiguration` 注解的配置信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // <3> 從所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解對應的類(需要自動配置的類)
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // <4> 對所有的自動配置類進行去重
    configurations = removeDuplicates(configurations);
    // <5> 獲取需要排除的自動配置類
    // 可通過 `@EnableAutoConfiguration` 注解的 `exclude` 和 `excludeName` 配置
    // 也可以通過 `spring.autoconfigure.exclude` 配置
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // <6> 處理 `exclusions` 中特殊的類名稱,保證能夠排除它
    checkExcludedClasses(configurations, exclusions);
    // <7> 從 `configurations` 中將 `exclusions` 需要排除的自動配置類移除
    configurations.removeAll(exclusions);
    /**
     * <8> 從 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportFilter} 對 `configurations` 進行過濾處理
     * 例如 Spring Boot 中配置了 {@link org.springframework.boot.autoconfigure.condition.OnClassCondition}
     * 在這里提前過濾掉一些不滿足條件的自動配置類,在 Spring 注入 Bean 的時候也會判斷哦~
     */
    configurations = filter(configurations, autoConfigurationMetadata);
    /**
     * <9> 從 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportListener} 事件監聽器
     * 觸發每個監聽器去處理 {@link AutoConfigurationImportEvent} 事件,該事件中包含了 `configurations` 和 `exclusions`
     * Spring Boot 中配置了一個 {@link org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener}
     * 目的就是將 `configurations` 和 `exclusions` 保存至 {@link AutoConfigurationImportEvent} 對象中,並注冊到 IoC 容器中,名稱為 `autoConfigurationReport`
     * 這樣一來我們可以注入這個 Bean 獲取到自動配置類信息
     */
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // <10> 將所有的自動配置類封裝成一個 AutoConfigurationEntry 對象,並返回
    return new AutoConfigurationEntry(configurations, exclusions);
}

過程如下:

  1. 如果通過 spring.boot.enableautoconfiguration 配置關閉了自動配置功能,則返回一個“空”的對象

  2. 獲取 @EnableAutoConfiguration 注解的配置信息

  3. 從所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解對應的類(需要自動配置的類),保存在 configurations 集合中

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // 從所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解對應的類(需要自動配置的類)
        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;
    }
    

    這個 SpringFactoriesLoader 是由 Spring 提供的一個類

  4. 對所有的自動配置類進行去重

    protected final <T> List<T> removeDuplicates(List<T> list) {
        return new ArrayList<>(new LinkedHashSet<>(list));
    }
    
  5. 獲取需要排除的自動配置類,可通過 @EnableAutoConfiguration 注解的 excludeexcludeName 配置,也可以通過 spring.autoconfigure.exclude 配置

  6. 處理 exclusions 中特殊的類名稱,保證能夠排除它

  7. configurations 中將 exclusions 需要排除的自動配置類移除

  8. 調用 filter(..) 方法, 目的就是過濾掉一些不符合 Condition 條件的自動配置類,和在 1. selectImports 方法 小節中講到的性能優化有關哦

  9. META-INF/spring.factories 找到所有的 AutoConfigurationImportListener 事件監聽器,觸發每個監聽器去處理 AutoConfigurationImportEvent 事件,該事件中包含了 configurationsexclusions

    Spring Boot 中配置了一個監聽器,目的就是將 configurationsexclusions 保存至 AutoConfigurationImportEvent 對象中,並注冊到 IoC 容器中,名稱為 autoConfigurationReport,這樣一來我們可以注入這個 Bean 獲取到自動配置類信息

  10. 將所有的自動配置類封裝成一個 AutoConfigurationEntry 對象,並返回

整個過程不復雜,關鍵在於上面的第 3 步和第 8 步,先從所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解對應的類(需要自動配置的類),然后進行過濾

3. filter 方法

filter(List<String>, AutoConfigurationMetadata) 方法,過濾一些自動配置類

我們得先知道這兩個入參:

  1. 所有的自動配置類名稱
  2. META-INF/spring-autoconfigure-metadata.properties 文件保存的 Spring Boot 的自動配置類的注解元信息(Sprng Boot 編譯時生成的)

這里的目的就是根據 2 里面的注解元信息,先過濾掉一些自動配置類

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    // <1> 將自動配置類保存至 `candidates` 數組中
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    /*
     * <2> 從 `META-INF/spring.factories` 找到所有的 AutoConfigurationImportFilter 對 `candidates` 進行過濾處理
     * 有 OnClassCondition、OnBeanCondition、OnWebApplicationCondition
     */
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // <2.1> Aware 回調
        invokeAwareMethods(filter);
        // <2.2> 對 `candidates` 進行匹配處理,獲取所有的匹配結果
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        // <2.3> 遍歷匹配結果,將不匹配的自動配置類至空
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    // <3> 如果沒有不匹配的結果則全部返回
    if (!skipped) {
        return configurations;
    }
    // <4> 獲取到所有匹配的自動配置類,並返回
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    return new ArrayList<>(result);
}

過程如下:

  1. 將自動配置類保存至 candidates 數組中

  2. META-INF/spring.factories 找到所有的 AutoConfigurationImportFiltercandidates 進行過濾處理

    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
    org.springframework.boot.autoconfigure.condition.OnClassCondition,\
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
    
    1. Aware 回調
    2. candidates 進行匹配處理,獲取所有的匹配結果,注意,這里傳入了 AutoConfigurationMetadata 對象
    3. 遍歷匹配結果,將不匹配的自動配置類至空
  3. 如果沒有不匹配的結果則全部返回

  4. 獲取到所有匹配的自動配置類,並返回

關鍵在於上面的第 2 步,通過 Spring Boot 自己擴展的幾個自動配置類過濾器進行過濾,由於這部分內容和 Spring Boot 拓展 Condition 相關,放入下篇文章進行分析

下面我們一起來看看上面 1. selectImports 方法 小節中講到的性能優化,META-INF/spring-autoconfigure-metadata.properties 文件是如何生成的,文件的內容又是什么

AutoConfigureAnnotationProcessor

org.springframework.boot.autoconfigureprocessor.AutoConfigureAnnotationProcessor,Spring Boot 的 spring-boot-autoconfigure-processor 工具模塊中的自動配置類的注解處理器,在編譯階段掃描自動配置類的注解元信息,並將他們保存至一個 properties 文件中

@SupportedAnnotationTypes({ "org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
		"org.springframework.boot.autoconfigure.condition.ConditionalOnBean",
		"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate",
		"org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication",
		"org.springframework.boot.autoconfigure.AutoConfigureBefore",
		"org.springframework.boot.autoconfigure.AutoConfigureAfter",
		"org.springframework.boot.autoconfigure.AutoConfigureOrder" })
public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
	/**
	 * 生成的文件
	 */
	protected static final String PROPERTIES_PATH = "META-INF/spring-autoconfigure-metadata.properties";
	/**
	 * 保存指定注解的簡稱和注解全稱之間的對應關系(不可修改)
	 */
	private final Map<String, String> annotations;

	private final Map<String, ValueExtractor> valueExtractors;

	private final Properties properties = new Properties();

	public AutoConfigureAnnotationProcessor() {
		// <1> 創建一個 Map 集合
		Map<String, String> annotations = new LinkedHashMap<>();
		// <1.1> 將指定注解的簡稱和全稱之間的對應關系保存至第 `1` 步創建的 Map 中
		addAnnotations(annotations);
		// <1.2> 將 `1.1` 的 Map 轉換成不可修改的 UnmodifiableMap 集合,賦值給 `annotations`
		this.annotations = Collections.unmodifiableMap(annotations);
		// <2> 創建一個 Map 集合
		Map<String, ValueExtractor> valueExtractors = new LinkedHashMap<>();
		// <2.1> 將指定注解的簡稱和對應的 ValueExtractor 對象保存至第 `2` 步創建的 Map 中
		addValueExtractors(valueExtractors);
		// <2.2> 將 `2.1` 的 Map 轉換成不可修改的 UnmodifiableMap 集合,賦值給 `valueExtractors`
		this.valueExtractors = Collections.unmodifiableMap(valueExtractors);
	}
}

AbstractProcessor 是 JDK 1.6 引入的一個抽象類,支持在編譯階段進行處理,在構造器中做了以下事情:

  1. 創建一個 Map 集合

    1. 將指定注解的簡稱和全稱之間的對應關系保存至第 1 步創建的 Map 中

      protected void addAnnotations(Map<String, String> annotations) {
          annotations.put("ConditionalOnClass", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass");
          annotations.put("ConditionalOnBean", "org.springframework.boot.autoconfigure.condition.ConditionalOnBean");
          annotations.put("ConditionalOnSingleCandidate", "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate");
          annotations.put("ConditionalOnWebApplication", "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication");
          annotations.put("AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureBefore");
          annotations.put("AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureAfter");
          annotations.put("AutoConfigureOrder", "org.springframework.boot.autoconfigure.AutoConfigureOrder");
      }
      
    2. 1.1 的 Map 轉換成不可修改的 UnmodifiableMap 集合,賦值給 annotations

  2. 創建一個 Map 集合

    1. 將指定注解的簡稱和對應的 ValueExtractor 對象保存至第 2 步創建的 Map 中

      private void addValueExtractors(Map<String, ValueExtractor> attributes) {
          attributes.put("ConditionalOnClass", new OnClassConditionValueExtractor());
          attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor());
          attributes.put("ConditionalOnSingleCandidate", new OnBeanConditionValueExtractor());
          attributes.put("ConditionalOnWebApplication", ValueExtractor.allFrom("type"));
          attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name"));
          attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name"));
          attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value"));
      }
      
    2. 2.1 的 Map 轉換成不可修改的 UnmodifiableMap 集合,賦值給 valueExtractors

getSupportedSourceVersion 方法

返回支持的 Java 版本

@Override
public SourceVersion getSupportedSourceVersion() {
    // 返回 Java 版本,默認 1.5
    return SourceVersion.latestSupported();
}

process 方法

處理過程

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    // <1> 遍歷上面的幾個 `@Conditional` 注解和幾個定義自動配置類順序的注解,依次進行處理
    for (Map.Entry<String, String> entry : this.annotations.entrySet()) {
        // <1.1> 對支持的注解進行處理,也就是找到所有標注了該注解的類,然后解析出該注解的值,保存至 Properties
        // 例如 `類名.注解簡稱` => `注解中的值(逗號分隔)` 和 `類名` => `空字符串`,將自動配置類的信息已經對應注解的信息都保存起來
        // 避免你每次啟動 Spring Boot 應用都要去解析自動配置類上面的注解,是引入 `spring-boot-autoconfigure` 后可以從 `META-INF/spring-autoconfigure-metadata.properties` 文件中直接獲取
        // 這么一想,Spring Boot 設計的太棒了,所以你自己寫的 Spring Boot Starter 中的自動配置模塊也可以引入這個 Spring Boot 提供的插件
        process(roundEnv, entry.getKey(), entry.getValue());
    }
    // <2> 如果處理完成
    if (roundEnv.processingOver()) {
        try {
            // <2.1> 將 Properties 寫入 `META-INF/spring-autoconfigure-metadata.properties` 文件
            writeProperties();
        }
        catch (Exception ex) {
            throw new IllegalStateException("Failed to write metadata", ex);
        }
    }
    // <3> 返回 `false`
    return false;
}

過程如下:

  1. 遍歷上面的幾個 @Conditional 注解和幾個定義自動配置類順序的注解,依次進行處理

    1. 調用 process(..) 重載方法,對支持的注解進行處理,也就是找到所有標注了該注解的類,然后解析出該注解的值,保存至 Properties
  2. 如果處理完成

    1. 調用 writeProperties() 方法,將 Properties 寫入 META-INF/spring-autoconfigure-metadata.properties 文件

      private void writeProperties() throws IOException {
          if (!this.properties.isEmpty()) {
              FileObject file = this.processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH);
              try (OutputStream outputStream = file.openOutputStream()) {
                  this.properties.store(outputStream, null);
              }
          }
      }
      

上面的第 1.1 步處理后的 Properties 包含以下內容:

  • 自動配置類的類名.注解簡稱 --> 注解中的值(逗號分隔)
  • 自動配置類的類名 --> 空字符串

通過后續寫入的文件,避免你每次啟動 Spring Boot 應用都要去解析自動配置類上面的注解,從而提高應用啟動時的效率

這么一想,Spring Boot 設計的太棒了,所以你自己寫的 Spring Boot Starter 中的自動配置模塊也可以引入這個 Spring Boot 提供的插件

process 重載方法

private void process(RoundEnvironment roundEnv, String propertyKey, String annotationName) {
    // <1> 獲取到這個注解名稱對應的 Java 類型
    TypeElement annotationType = this.processingEnv.getElementUtils().getTypeElement(annotationName);
    if (annotationType != null) {
        // <2> 如果存在該注解,則從 RoundEnvironment 中獲取標注了該注解的所有 Element 元素,進行遍歷
        for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
            // <2.1> 獲取這個 Element 元素 innermost 最深處的 Element
            Element enclosingElement = element.getEnclosingElement();
            // <2.2> 如果最深處的 Element 的類型是 PACKAGE 包,那么表示這個元素是一個類,則進行處理
            if (enclosingElement != null && enclosingElement.getKind() == ElementKind.PACKAGE) {
                // <2.2.1> 解析這個類上面 `annotationName` 注解的信息,並保存至 `properties` 中
                processElement(element, propertyKey, annotationName);
            }
        }
    }
}

過程如下:

  1. 獲取到這個注解名稱對應的 Java 類型
  2. 如果存在該注解,則從 RoundEnvironment 中獲取標注了該注解的所有 Element 元素,進行遍歷
    1. 獲取這個 Element 元素 innermost 最深處的 Element
    2. 如果最深處的 Element 的類型是 PACKAGE 包,那么表示這個元素是一個類,則進行處理
      1. 調用 processElement(..) 方法,解析這個類上面 annotationName 注解的信息,並保存至 properties

processElement 方法

private void processElement(Element element, String propertyKey, String annotationName) {
    try {
        // <1> 獲取這個類的名稱
        String qualifiedName = Elements.getQualifiedName(element);
        // <2> 獲取這個類上面的 `annotationName` 類型的注解信息
        AnnotationMirror annotation = getAnnotation(element, annotationName);
        if (qualifiedName != null && annotation != null) {
            // <3> 獲取這個注解中的值
            List<Object> values = getValues(propertyKey, annotation);
            // <4> 往 `properties` 中添加 `類名.注解簡稱` => `注解中的值(逗號分隔)`
            this.properties.put(qualifiedName + "." + propertyKey, toCommaDelimitedString(values));
            // <5> 往 `properties` 中添加 `類名` => `空字符串`
            this.properties.put(qualifiedName, "");
        }
    }
    catch (Exception ex) {
        throw new IllegalStateException("Error processing configuration meta-data on " + element, ex);
    }
}

過程如下:

  1. 獲取這個類的名稱

  2. 獲取這個類上面的 annotationName 類型的注解信息

    private AnnotationMirror getAnnotation(Element element, String type) {
        if (element != null) {
            for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
                if (type.equals(annotation.getAnnotationType().toString())) {
                    return annotation;
                }
            }
        }
        return null;
    }
    
  3. 獲取這個注解中的值

    private List<Object> getValues(String propertyKey, AnnotationMirror annotation) {
        // 獲取該注解對應的 value 抽取器
        ValueExtractor extractor = this.valueExtractors.get(propertyKey);
        if (extractor == null) {
            return Collections.emptyList();
        }
        // 獲取這個注解中的值,並返回
        return extractor.getValues(annotation);
    }
    
  4. properties 中添加 類名.注解簡稱 --> 注解中的值(逗號分隔)

  5. properties 中添加 類名 --> 空字符串

總結

本文分析了 @SpringBootApplication 組合注解,它是 @SpringBootConfiguration@ComponentScan@EnableAutoConfiguration 幾個注解的組合注解,對於前兩個注解我想你並不陌生,分析 Spring 源碼的時候差不多已經講過,最后一個注解則是 Spring Boot 自動配置功能的驅動注解,也是本文講述的一個重點。

@EnableAutoConfiguration 注解的實現原理並不復雜,借助於 @Import 注解,從所有 META-INF/spring.factories 文件中找到 org.springframework.boot.autoconfigure.EnableAutoConfiguration 對應的值,例如:

會將這些自動配置類作為一個 Bean 嘗試注入到 Spring IoC 容器中,注入的時候 Spring 會通過 @Conditional 注解判斷是否符合條件,因為並不是所有的自動配置類都滿足條件。當然,Spring Boot 對 @Conditional 注解進行了擴展,例如 @ConditionalOnClass 可以指定必須存在哪些 Class 對象才注入這個 Bean。

Spring Boot 會借助 spring-boot-autoconfigure-processor 工具模塊在編譯階段將自己的自動配置類的注解元信息保存至一個 properties 文件中,避免每次啟動應用都要去解析這么多自動配置類上面的注解。同時會通過幾個過濾器根據這個 properties 文件過濾掉一些自動配置類,具體怎么過濾的會在下面文章講到。

好了,總結下來就是 Spring Boot 會從 META-INF/spring.factories 文件中找到配置的自動配置類,然后根據 Condition 條件進行注入,如果注入的話則可以通過 @EnableXxx 模塊驅動注解驅動某個模塊,例如 Spring AOP 模塊。那么關於 Spring Boot 對 Spring Condition 的擴展在下篇文章進行分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM