日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Boot - 自动配置实现原理

發布時間:2025/3/21 javascript 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Boot - 自动配置实现原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • Pre
  • @SpringBootApplication 注解
    • @ComponentScan 注解
    • @SpringBootConfiguration 注解
    • @EnableAutoConfiguration 注解
      • @AutoConfigurationPackage
        • @Import
      • @Import(AutoConfigurationImportSelector.class)
  • SPI 機制和 SpringFactoriesLoader
    • JDK 中的 SPI 機制
    • SpringFactoriesLoader
  • @ConditionalOn 系列條件注解
  • @ConditionalOn 系列條件注解的實現原理
  • 小結


Pre

Spring Boot 中的配置體系是一套強大而復雜的體系,其中最基礎、最核心的要數自動配置(AutoConfiguration)機制了。

今天我們將圍繞這個話題詳細展開討論,看看 Spring Boot 如何實現自動配置。那我們就先從 @SpringBootApplication 注解開始講起。


@SpringBootApplication 注解

@SpringBootApplication 注解位于** spring-boot-autoconfigure** 工程的 org.springframework.boot.autoconfigure 包中,定義如下:

@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 {@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;}

相較一般的注解,@SpringBootApplication 注解顯得有點復雜。我們可以

  • 通過 exclude 和 excludeName 屬性來配置不需要實現自動裝配的類或類名,
  • 也可以通過 scanBasePackages 和 scanBasePackageClasses 屬性來配置需要進行掃描的包路徑和類路徑。

注意到 @SpringBootApplication 注解實際上是一個組合注解,它由三個注解組合而成,分別是 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan。

@ComponentScan 注解

@ComponentScan 注解不是 Spring Boot 引入的新注解,而是屬于 Spring 容器管理的內容。@ComponentScan 注解就是掃描基于 @Component 等注解所標注的類所在包下的所有需要注入的類,并把相關 Bean 定義批量加載到容器中。顯然,Spring Boot 應用程序中同樣需要這個功能。


@SpringBootConfiguration 注解

@SpringBootConfiguration 注解比較簡單,事實上它是一個空注解,只是使用了 Spring 中的 @Configuration 注解。@Configuration 注解比較常見,提供了 JavaConfig 配置類實現。


@EnableAutoConfiguration 注解

@EnableAutoConfiguration 注解是我們需要重點剖析的對象,下面進行重點展開。該注解的定義如下代碼所示:

@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 {};}

這里我們關注兩個新注解,@AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class)。

@AutoConfigurationPackage

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

從命名上講,在這個注解中我們對該注解所在包下的類進行自動配置,而在實現方式上用到了 Spring 中的 @Import 注解。在使用 Spring Boot 時,@Import 也是一個非常常見的注解,可以用來動態創建 Bean。為了便于理解后續內容,這里有必要對 @Import 注解的運行機制做一些展開,該注解定義如下:

@Import

@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import {Class<?>[] value(); }

在 @Import 注解的屬性中可以設置需要引入的類名,例如 @AutoConfigurationPackage 注解上的 @Import(AutoConfigurationPackages.Registrar.class)。根據該類的不同類型,Spring 容器針對 @Import 注解有以下四種處理方式:

  • 如果該類實現了 ImportSelector 接口,Spring 容器就會實例化該類,并且調用其 selectImports 方法;
  • 如果該類實現了 DeferredImportSelector 接口,則 Spring 容器也會實例化該類并調用其 selectImports方法。DeferredImportSelector 繼承了 ImportSelector,區別在于 DeferredImportSelector 實例的 selectImports 方法調用時機晚于 ImportSelector 的實例,要等到 @Configuration 注解中相關的業務全部都處理完了才會調用;
  • 如果該類實現了 ImportBeanDefinitionRegistrar 接口,Spring 容器就會實例化該類,并且調用其 registerBeanDefinitions 方法;
  • 如果該類沒有實現上述三種接口中的任何一個,Spring 容器就會直接實例化該類。

有了對 @Import 注解的基本理解,我們再來看 AutoConfigurationPackages.Registrar 類,定義如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {register(registry, new PackageImport(metadata).getPackageName());}@Overridepublic Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImport(metadata));}}

可以看到這個 Registrar 類實現了前面第三種情況中提到的 ImportBeanDefinitionRegistrar 接口并重寫了 registerBeanDefinitions 方法,該方法中調用 AutoConfigurationPackages 自身的 register 方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {if (registry.containsBeanDefinition(BEAN)) {BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();constructorArguments.addIndexedArgumentValue(0,addBasePackages(constructorArguments, packageNames));}else {GenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(BasePackages.class);beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,packageNames);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registry.registerBeanDefinition(BEAN, beanDefinition);} }

這個方法的邏輯是先判斷整個 Bean 有沒有被注冊,如果已經注冊則獲取 Bean 的定義,通過 Bean 獲取構造函數的參數并添加參數值;如果沒有,則創建一個新的 Bean 的定義,設置 Bean 的類型為 AutoConfigurationPackages 類型并進行 Bean 的注冊。


@Import(AutoConfigurationImportSelector.class)

然后我們再來看 @EnableAutoConfiguration 注解中的 @Import(AutoConfigurationImportSelector.class) 部分,首先我們明確 AutoConfigurationImportSelector 類實現了 @Import 注解第二種情況中的 DeferredImportSelector 接口,所以會執行如下所示的 selectImports 方法:

@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);//獲取 configurations 集合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);}

這段代碼的核心是通過 getCandidateConfigurations 方法獲取 configurations 集合并進行過濾。

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;}

Assert 校驗,該校驗是一個非空校驗,會提示 “在 META-INF/spring.factories 中沒有找到自動配置類” 這個異常信息。

看到這里,不得不提到 JDK 中的 SPI 機制,因為無論從 SpringFactoriesLoader 這個類的命名上,還是 META-INF/spring.factories 這個文件目錄,兩者之間都存在很大的相通性。

從類名上看,**AutoConfigurationImportSelector 類是一種選擇器,負責從各種配置項中找到需要導入的具體配置類。**如下圖所示

顯然,AutoConfigurationImportSelector 所依賴的最關鍵組件就是 SpringFactoriesLoader,下面我們對其進行具體展開。


SPI 機制和 SpringFactoriesLoader

要想理解 SpringFactoriesLoader 類,我們首先需要了解 JDK 中 SPI(Service Provider Interface,服務提供者接口)機制。

JDK 中的 SPI 機制

JDK 提供了用于服務查找的一個工具類 java.util.ServiceLoader 來實現 SPI 機制。當服務提供者提供了服務接口的一種實現之后,我們可以在 jar 包的 META-INF/services/ 目錄下創建一個以服務接口命名的文件,該文件里配置著一組 Key-Value,用于指定服務接口與實現該服務接口具體實現類的映射關系。而當外部程序裝配這個 jar 包時,就能通過該 jar 包 META-INF/services/ 目錄中的配置文件找到具體的實現類名,并裝載實例化,從而完成模塊的注入。

SPI 提供了一種約定,基于該約定就能很好地找到服務接口的實現類,而不需要在代碼里硬編碼指定。

JDK 中 SPI 機制開發流程如下圖所示:


SpringFactoriesLoader

SpringFactoriesLoader 類似這種 SPI 機制,只不過以服務接口命名的文件是放在 META-INF/spring.factories 文件夾下,對應的 Key 為 EnableAutoConfiguration。SpringFactoriesLoader 會查找所有 META-INF/spring.factories 文件夾中的配置文件,并把 Key 為 EnableAutoConfiguration 所對應的配置項通過反射實例化為配置類并加載到容器中。

這一點我們可以在 SpringFactoriesLoader 的 loadSpringFactories 方法中進行印證:

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;}

查看看 getSpringFactoriesLoaderFactoryClass()

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

可知 Key 為 EnableAutoConfiguration

緊接著看

return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());

繼續 loadSpringFactories

以下就是 spring-boot-autoconfigure 工程中所使用的 spring.factories 配置文件片段,可以看到 EnableAutoConfiguration 項中包含了各式各樣的配置項,這些配置項在 Spring Boot 啟動過程中都能夠通過 SpringFactoriesLoader 加載到運行時環境,從而實現自動化配置:

以上就是 Spring Boot 中基于 @SpringBootApplication 注解實現自動配置的基本過程和原理。當然,@SpringBootApplication 注解也可以基于外部配置文件加載配置信息。基于約定優于配置思想,Spring Boot 在加載外部配置文件的過程中大量使用了默認配置。


@ConditionalOn 系列條件注解

Spring Boot 默認提供了 100 多個 AutoConfiguration 類,顯然我們不可能會全部引入。所以在自動裝配時,系統會去類路徑下尋找是否有對應的配置類。如果有對應的配置類,則按條件進行判斷,決定是否需要裝配。這里就引出了在閱讀 Spring Boot 代碼時經常會碰到的另一批注解,即 @ConditionalOn 系列條件注解。

我們先通過一個簡單的示例來了解 @ConditionalOn 系列條件注解的使用方式,例如以下代碼就是這類注解的一種典型應用,該代碼位于 Spring Cloud Config 的客戶端代碼工程 spring-cloud-config-client 中:

@Bean @ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class) @ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator( properties);return locator;}

可以看到,這里運用了兩個 @ConditionalOn 注解,一個是 @ConditionalOnMissingBean,一個是 @ConditionalOnProperty。

再比如在 Spring Cloud Config 的服務器端代碼工程 spring-cloud-config-server 中,存在如下 ConfigServerAutoConfiguration 自動配置類:

@Configuration @ConditionalOnBean(ConfigServerConfiguration.Marker.class) @EnableConfigurationProperties(ConfigServerProperties.class) @Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class }) public class ConfigServerAutoConfiguration {}

這里我們運用了 @ConditionalOnBean 注解。實際上,Spring Boot 中提供了一系列的條件注解,常見的包括:

  • @ConditionalOnProperty:只有當所提供的屬性屬于 true 時才會實例化 Bean

  • @ConditionalOnBean:只有在當前上下文中存在某個對象時才會實例化 Bean

  • @ConditionalOnClass:只有當某個 Class 位于類路徑上時才會實例化 Bean

  • @ConditionalOnExpression:只有當表達式為 true 的時候才會實例化 Bean

  • @ConditionalOnMissingBean:只有在當前上下文中不存在某個對象時才會實例化 Bean

  • @ConditionalOnMissingClass:只有當某個 Class 在類路徑上不存在的時候才會實例化 Bean

  • @ConditionalOnNotWebApplication:只有當不是 Web 應用時才會實例化 Bean

當然 Spring Boot 還提供了一些不大常用的 @ConditionalOnXXX 注解,這些注解都定義在 org.springframework.boot.autoconfigure.condition 包中。

顯然上述 ConfigServicePropertySourceLocator 類中只有在 “spring.cloud.config.enabled” 屬性為 true(通過 matchIfMissing 配置項表示默認即為 true)以及類路徑上不存在 ConfigServicePropertySourceLocator 時才會進行實例化。

而 ConfigServerAutoConfiguration 只有在類路徑上存在 ConfigServerConfiguration.Marker 類時才會進行實例化,這是一種常用的自動配置控制技巧。


@ConditionalOn 系列條件注解的實現原理

@ConditionalOn 系列條件注解非常多,我們無意對所有這些組件進行展開。事實上這些注解的實現原理也大致相同,我們只需要深入了解其中一個就能做到觸類旁通。這里我們挑選 @ConditionalOnClass 注解進行展開,該注解定義如下:

@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass {Class<?>[] value() default {};String[] name() default {}; }

可以看到, @ConditionalOnClass 注解本身帶有兩個屬性,一個 Class 類型的 value,一個 String 類型的 name,所以我們可以采用這兩種方式中的任意一種來使用該注解。同時 ConditionalOnClass 注解本身還帶了一個 @Conditional(OnClassCondition.class) 注解。所以, ConditionalOnClass 注解的判斷條件其實就包含在 OnClassCondition 這個類中。

OnClassCondition 是 SpringBootCondition 的子類,而 SpringBootCondition 又實現了Condition 接口。

Condition 接口只有一個 matches 方法,如下所示:

@FunctionalInterface public interface Condition {/*** Determine if the condition matches.* @param context the condition context* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}* or {@link org.springframework.core.type.MethodMetadata method} being checked* @return {@code true} if the condition matches and the component can be registered,* or {@code false} to veto the annotated component's registration*/boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}

SpringBootCondition 中的 matches 方法實現如下:


這里的 getClassOrMethodName 方法獲取被添加了@ConditionalOnClass 注解的類或者方法的名稱,而 getMatchOutcome 方法用于獲取匹配的輸出。

我們看到 getMatchOutcome 方法實際上是一個抽象方法,需要交由 SpringBootCondition 的各個子類完成實現,這里的子類就是 OnClassCondition 類。

在理解 OnClassCondition 時,我們需要明白在 Spring Boot 中,@ConditionalOnClass 或者 @ConditionalOnMissingClass 注解對應的條件類都是 OnClassCondition,所以在 OnClassCondition 的 getMatchOutcome 中會同時處理兩種情況。這里我們挑選處理 @ConditionalOnClass 注解的代碼,核心邏輯如下所示:

@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ClassLoader classLoader = context.getClassLoader();ConditionMessage matchMessage = ConditionMessage.empty();List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);if (onClasses != null) {List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);if (!missing.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class).didNotFind("required class", "required classes").items(Style.QUOTE, missing));}matchMessage = matchMessage.andCondition(ConditionalOnClass.class).found("required class", "required classes").items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));}List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);if (onMissingClasses != null) {List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);if (!present.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));}matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));}return ConditionOutcome.match(matchMessage);}

這里有兩個方法值得注意,一個是 getCandidates 方法,一個是 getMatches 方法。首先通過 getCandidates 方法獲取了 ConditionalOnClass 的 name 屬性和 value 屬性。然后通過 getMatches 方法將這些屬性值進行比對,得到這些屬性所指定的但在類加載器中不存在的類。如果發現類加載器中應該存在但事實上又不存在的類,則返回一個匹配失敗的 Condition;反之,如果類加載器中存在對應類的話,則把匹配信息進行記錄并返回一個 ConditionOutcome。


小結

自動配置是 Spring Boot 最核心和最基本的功能,而 @SpringBootApplication 注解又是 Spring Boot 應用程序的入口。本課時從 @SpringBootApplication 注解入手,分析了自動配置機制的實現過程。涉及的知識點比較多,包含 JDK 中的 SPI 機制,以及 @ConditionalOn 系列條件注解。

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的Spring Boot - 自动配置实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。