朱曄和你聊Spring系列S1E2:SpringBoot並不神秘
文本我們會一步一步做一個例子來看看SpringBoot的自動配置是如何實現的,然后來看一些SpringBoot留給我們的擴展點。
自己制作一個SpringBoot Starter
我們知道SpringBoot提供了非常多的啟動器,引入了啟動器依賴即可直接享受到自動依賴配置和自動屬性配置:

https://github.com/spring-projects/spring-boot/tree/master/spring-boot-project/spring-boot-starters
在第一篇文章中我提到,在SpringBoot出現之前,我們需要使用SpringMVC、Spring Data、Spring Core都需要對Spring內部的各種組件進行Bean以及Bean依賴的配置,在90%的時候我們用的是默認的配置,不會自定義任何擴展類,這個時候也需要由使用者來手動配置顯然不合理,有了SpringBoot,我們只需引入啟動器依賴,然后啟動器就可以自己做為自己的一些內部組件做自動配置,大大方便了使用者。啟動器的實現非常簡單,我們來看下實現過程。
首先創建一個Maven空項目,引入SpringBoot:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>me.josephzhu</groupId> <artifactId>spring101</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring101</name> <description></description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
然后我們創建一個Starter模塊隸屬於父項目:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>me.josephzhu</groupId> <artifactId>spring101-customstarter</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring101-customstarter</name> <description></description> <parent> <groupId>me.josephzhu</groupId> <artifactId>spring101</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> </dependencies> </project>
接下去我們創建一個服務抽象基類和實現,這個服務非常簡單,會依賴到一些屬性,然后會有不同的實現(無參構造函數設置了打招呼用語的默認值為Hello):
package me.josephzhu.spring101customstarter; import org.springframework.beans.factory.annotation.Autowired; public abstract class AbstractMyService { protected String word; public AbstractMyService(String word) { this.word = word; } public AbstractMyService() { this ("Hello"); } @Autowired protected MyServiceProperties properties; public abstract String hello(); }
這里注入了自定義屬性類:
package me.josephzhu.spring101customstarter; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring101") @Data public class MyServiceProperties { /** * user name */ private String name; /** * user age *Should between 1 and 120 */ private Integer age; /** * determine the service version you want use */ private String version; }
這里看到了如果我們需要定義一個自定義類來關聯配置源(比如application.properties文件配置)是多么簡單,使用@ConfigurationProperties注解標注我們的POJO告知注解我們配置的前綴即可。額外提一句,如果希望我們的IDE可以針對自定義配置有提示的話(自動完成,而且帶上注解中的提示語),可以引入如下的依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
這樣編譯后就會在META-INF下面生成一個叫做spring-configuration-metadata.json的文件:
{
"hints": [],
"groups": [
{
"sourceType": "me.josephzhu.spring101customstarter.MyServiceProperties",
"name": "spring101",
"type": "me.josephzhu.spring101customstarter.MyServiceProperties"
}
],
"properties": [
{
"sourceType": "me.josephzhu.spring101customstarter.MyServiceProperties",
"name": "spring101.age",
"description": "user age *Should between 1 and 120",
"type": "java.lang.Integer"
},
{
"sourceType": "me.josephzhu.spring101customstarter.MyServiceProperties",
"name": "spring101.name",
"description": "user name",
"type": "java.lang.String"
},
{
"sourceType": "me.josephzhu.spring101customstarter.MyServiceProperties",
"name": "spring101.version",
"description": "determine the service version you want use",
"type": "java.lang.String"
}
]
}
之后在使用配置的時候就可以有提示:

我們先來寫第一個服務實現,如下,只是輸出一下使用到的一些自定義屬性:
package me.josephzhu.spring101customstarter; import org.springframework.stereotype.Service; @Service public class MyService extends AbstractMyService { public MyService(String word) { super(word); } public MyService(){} @Override public String hello() { return String.format("V1 %s >> %s:%s !!", word, properties.getName(), properties.getAge()); } }
關鍵的一步來了,接下去我們需要定義自動配置類:
package me.josephzhu.spring101customstarter; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(MyServiceProperties.class) public class MyAutoConfiguration { @Bean MyService getMyService(){ return new MyService(); } }
通過EnableConfigurationProperties來告訴Spring我們需要關聯一個配置文件配置類(配置類斗不需要設置@Component),通過@Configuration告訴Spring這是一個Bean的配置類,下面我們定義了我們Service的實現。
最后,我們需要告訴SpringBoot如何來找到我們的自動配置類,在合適的時候自動配置。我們需要在項目資源目錄建一個META-INF文件夾,然后創建一個spring.factories文件,寫入下面的內容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=me.josephzhu.spring101customstarter.MyAutoConfiguration
好了,就是這么簡單,接下去我們創建一個項目來使用我們的自定義啟動器來試試:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>me.josephzhu</groupId> <artifactId>spring101-main</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring101-main</name> <description></description> <parent> <groupId>me.josephzhu</groupId> <artifactId>spring101</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>me.josephzhu</groupId> <artifactId>spring101-customstarter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
創建一個Runner來調用服務:
package me.josephzhu.spring101main; import lombok.extern.slf4j.Slf4j; import me.josephzhu.spring101customstarter.AbstractMyService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component @Slf4j public class Runner1 implements CommandLineRunner { @Autowired private AbstractMyService service; @Override public void run(String... args) { log.info(service.hello()); } }
創建主程序:
package me.josephzhu.spring101main; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Spring101MainApplication { public static void main(String[] args) { SpringApplication.run(Spring101MainApplication.class, args); } }
然后在main模塊的資源目錄下創建application.properties文件,寫入兩個配置:
spring101.age=35
spring101.name=zhuye
運行后可以看到輸出:
2018-09-30 14:55:00.848 INFO 12704 --- [ main] me.josephzhu.spring101main.Runner1 : V1 Hello >> zhuye:35 !!
可以證明,第一我們的main模塊引入的starter正確被SpringBoot識別加載,第二starter中的Configuration正確執行不但加載了配置類,而且也正確注入了Service的一個實現。
如何實現條件配置?
作為組件的開發者,我們有的時候希望針對環境、配置、類的加載情況等等進行各種更智能的自動配置,這個時候就需要使用Spring的Conditional特性。我們來看一個例子,如果我們的Service隨着發展演化出了v2版本,我們希望用戶在默認的時候使用v1,如果需要的話可以進行version屬性配置允許用戶切換到v2版本。實現起來非常簡單,首先定義另一個v2版本的服務:
package me.josephzhu.spring101customstarter; import org.springframework.stereotype.Service; @Service public class MyServiceV2 extends AbstractMyService { public MyServiceV2(String word) { super(word); } public MyServiceV2(){} @Override public String hello() { return String.format("V2 %s >> %s:%s !!", word, properties.getName(), properties.getAge()); } }
和版本v1沒有任何區別,只是標記了一下v2關鍵字。
然后我們改造一下我們的自動配置類:
package me.josephzhu.spring101customstarter; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(MyServiceProperties.class) public class MyAutoConfiguration { @Bean @ConditionalOnProperty(prefix = "spring101", name = "version", havingValue = "v1", matchIfMissing = true) MyService getMyService(){ return new MyService(); } @Bean @ConditionalOnProperty(prefix = "spring101", name = "version", havingValue = "v2") MyServiceV2 getMyServiceV2(){ return new MyServiceV2(); } }
這里主要是為兩個Bean分別添加了@ConditionalOnProperty注解,注解是自解釋的。這里說了如果version的值是v1或沒有定義version的話匹配到默認的v1版本的服務,如果配置設置為v2的話匹配到v2版本的服務,就這么簡單。
再來看一個例子,如果我們的使用者希望自己定義服務的實現,這個時候我們需要覆蓋自動配置為我們自動裝配的v1和v2,可以使用另一個注解@ConditionalOnMissingBean來告知SpringBoot,如果找不到Bean的話再來自動配置:
package me.josephzhu.spring101customstarter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(MyServiceProperties.class) public class MyAutoConfiguration { @Bean @ConditionalOnMissingBean(MyService.class) @ConditionalOnProperty(prefix = "spring101", name = "version", havingValue = "v1", matchIfMissing = true) MyService getMyService(){ return new MyService(); } @Bean @ConditionalOnMissingBean(MyServiceV2.class) @ConditionalOnProperty(prefix = "spring101", name = "version", havingValue = "v2") MyServiceV2 getMyServiceV2(){ return new MyServiceV2(); } }
這樣的話,如果客戶端自己定義了Service的實現的話,就可以讓自動配置放棄自動配置使用客戶端自己定義的Bean。還有N多的Conditional注解可以使用,甚至可以自定義條件,具體可以查看官方文檔。
進行一下測試
接下去,我們來寫一下單元測試來驗證一下我們之前的代碼,使用ApplicationContextRunner可以方便得設置帶入的各種外部配置項以及自定義配置類:
在這里我們寫了三個測試用例:
- 在提供了合適的屬性配置后,可以看到服務的輸出正確獲取到了屬性。
- 使用配置項version來切換服務的實現,在省略version,設置version為1,設置version為2的情況下得到正確的輸出,分別是v1、v1和v2。
- 在客戶端自定義實現(MyServiceConfig)后可以看到並沒有加載使用自動配置里定義的服務實現,最后輸出了打招呼用語Hi而不是Hello。
package me.josephzhu.spring101main; import me.josephzhu.spring101customstarter.AbstractMyService; import me.josephzhu.spring101customstarter.MyAutoConfiguration; import me.josephzhu.spring101customstarter.MyService; import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; public class Spring101MainApplicationTests { private ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class)); @Test public void testService() { applicationContextRunner .withPropertyValues("spring101.age=35") .withPropertyValues("spring101.name=zhuye") .run(context -> { assertThat(context).hasSingleBean(AbstractMyService.class); assertThat(context.getBean(AbstractMyService.class).hello()).containsSequence("zhuye:35"); System.out.println(context.getBean(MyService.class).hello()); }); } @Test public void testConditionalOnProperty() { applicationContextRunner .run(context -> { assertThat(context).hasSingleBean(AbstractMyService.class); assertThat(context.getBean(AbstractMyService.class).hello()).containsSequence("V1 Hello"); System.out.println(context.getBean(AbstractMyService.class).hello()); }); applicationContextRunner .withPropertyValues("spring101.version=v1") .run(context -> { assertThat(context).hasSingleBean(AbstractMyService.class); assertThat(context.getBean(AbstractMyService.class).hello()).containsSequence("V1 Hello"); System.out.println(context.getBean(AbstractMyService.class).hello()); }); applicationContextRunner .withPropertyValues("spring101.version=v2") .run(context -> { assertThat(context).hasSingleBean(AbstractMyService.class); assertThat(context.getBean(AbstractMyService.class).hello()).containsSequence("V2 Hello"); System.out.println(context.getBean(AbstractMyService.class).hello()); }); } @Test public void testConditionalOnMissingBean() { applicationContextRunner .withUserConfiguration(MyServiceConfig.class) .run(context -> { assertThat(context).hasSingleBean(MyService.class); assertThat(context.getBean(MyService.class).hello()).containsSequence("V1 Hi"); System.out.println(context.getBean(MyService.class).hello()); }); } } @Configuration class MyServiceConfig { @Bean MyService getService() { return new MyService("Hi"); } }
運行測試可以看到三個測試都可以通過,控制台也輸出了hello方法的返回值:

實現自定義的配置數據源
接下去我們來看一下如何利用EnvironmentPostProcessor來實現一個自定義的配置數據源。我們在starter項目中新建一個類,這個類使用了一個Yaml配置源加載器,然后我們把加載到的自定義的PropertySource加入到PropertySource候選列表的第一個,這樣就可以實現屬性優先從我們定義的(classpath下的)config.yml來讀取:
package me.josephzhu.spring101customstarter; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; public class MyPropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor { private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { PropertySource<?> propertySource = loadYaml(new ClassPathResource("config.yml")); environment.getPropertySources().addFirst(propertySource); } private PropertySource<?> loadYaml(Resource path) { if (!path.exists()) { throw new IllegalArgumentException("Resource " + path + " does not exist"); } try { return this.loader.load(path.getFile().getAbsolutePath(), path).get(0); } catch (Exception ex) { throw new IllegalStateException("Failed to load yaml configuration from " + path, ex); } } }
最關鍵的一步就是讓SpringBoot能加載到我們這個PostProcessor,還是老樣子,在spring,factories文件中加入一項配置即可:
org.springframework.boot.env.EnvironmentPostProcessor=me.josephzhu.spring101customstarter.MyPropertySourceEnvironmentPostProcessor
現在,我們可以在starter項目下的resrouces目錄下創建一個config.yml來驗證一下:
spring101:
name: zhuye_yml
重新運行main項目可以看到如下的輸出結果中包含了yml字樣:
2018-09-30 15:27:05.123 INFO 12769 --- [ main] me.josephzhu.spring101main.Runner1 : V1 Hello >> zhuye_yml:35 !!
我們可以為項目添加一下Actuator模塊進行進一步驗證:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在配置文件中加入設置來放開所有的端口訪問:
management.endpoints.web.exposure.include=*
然后打開瀏覽器訪問http://127.0.0.1:8080/actuator/env:

可以看到,的確是添加了我們自定義的config.yml作為PropertySource。
自動配置的調試問題
對於復雜的項目,如果我們發現自動配置不起作用,要搞清楚框架是如何在各種條件中做自動配置以及自動配置的匹配過程是比較麻煩的事情,這個時候我們可以打開SpringBoot的Debug來查看日志:

我們可以在日志中搜索我們關注的類型的匹配情況,是不是很直觀呢:
MyAutoConfiguration#getMyService matched:
- @ConditionalOnProperty (spring101.version=v1) matched (OnPropertyCondition)
- @ConditionalOnMissingBean (types: me.josephzhu.spring101customstarter.MyService; SearchStrategy: all) did not find any beans (OnBeanCondition)
MyAutoConfiguration#getMyServiceV2:
Did not match:
- @ConditionalOnProperty (spring101.version=v2) did not find property 'version' (OnPropertyCondition)
我們可以試試在出錯的時候系統給我們的提示,來把配置文件中的version設置為3:
spring101.version=v3
重新運行后看到如下輸出:
***************************
APPLICATION FAILED TO START
**************************
Description:
Field service in me.josephzhu.spring101main.Runner1 required a bean of type 'me.josephzhu.spring101customstarter.AbstractMyService' that could not be found.
- Bean method 'getMyService' in 'MyAutoConfiguration' not loaded because @ConditionalOnProperty (spring101.version=v1) found different value in property 'version'
- Bean method 'getMyServiceV2' in 'MyAutoConfiguration' not loaded because @ConditionalOnProperty (spring101.version=v2) found different value in property 'version'
Action:
Consider revisiting the entries above or defining a bean of type 'me.josephzhu.spring101customstarter.AbstractMyService' in your configuration.
這個所謂的分析報告是比較機械性的,作為框架的開發者,我們甚至可以自定義叫做FailureAnalyzer的東西來做更明確的提示。實現上和之前的步驟幾乎一樣,首先自定義一個類:
package me.josephzhu.spring101customstarter; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; public class MyFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchBeanDefinitionException> { @Override protected FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause) { if(cause.getBeanType().equals(AbstractMyService.class)) return new FailureAnalysis("加載MyService失敗", "請檢查配置文件中的version屬性設置是否是v1或v2", rootFailure); return null; } }
這里我們根據cause的Bean類型做了簡單判斷,如果發生錯誤的是我們的Service類型的話,告知使用者明確的錯誤原因(Description)以及怎么來糾正這個錯誤(Action)。
然后老規矩,在spring.factories中進行關聯:
org.springframework.boot.diagnostics.FailureAnalyzer=me.josephzhu.spring101customstarter.MyFailureAnalyzer
重新運行程序后可以看到如下的結果:
***************************
APPLICATION FAILED TO START
***************************
Description:
加載MyService失敗
Action:
請檢查配置文件中的version屬性設置是否是v1或v2
是不是直觀很多呢?這里我的實現比較簡單,在正式的實現中你可以根據上下文以及環境的各種情況為用戶進行全面的分析,分析服務啟動失敗的原因。
SpringBoot的擴展點
在之前的幾個例子中,我們進行了各種擴展配置,通過spring.factories進行了自動配置、環境后處理器配置以及錯誤分析器配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=me.josephzhu.spring101customstarter.MyAutoConfiguration
org.springframework.boot.env.EnvironmentPostProcessor=me.josephzhu.spring101customstarter.MyPropertySourceEnvironmentPostProcessor
org.springframework.boot.diagnostics.FailureAnalyzer=me.josephzhu.spring101customstarter.MyFailureAnalyzer
其實,SpringBoot還有一些其它的擴展槽,如下是SpringBoot自帶的一些配置類:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesEnvironmentPostProcessor
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# 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
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
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
# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
我們可以看到一共有8大類的擴展槽,這些槽貫穿了整個SpringBoot加載的整個過程,感興趣的讀者可以逐一搜索查看Spring的文檔和源碼進行進一步的分析。
本文從如何做一個Starter實現自動配置開始,進一步闡述了如何實現智能的條件配置,如何,如何進行自動配置的測試,然后我們自定義了環境后處理器來加載額外的配置源(你當然可以實現更復雜的配置源,比如從Redis和數據庫中獲取配置)以及通過開啟Actutor來驗證,定義了配置錯誤分析器來給用戶明確的錯誤提示。最后,我們看了一下spring.factories中的內容了解了SpringBoot內部定義的一些擴展槽和實現。
