詳解 Spring Boot 自動配置原理和應用
我們知道,基于 Spring Boot,開發(fā)人員只需要在類路徑中引入一組第三方框架的 starter 組件,就能在 Spring 容器中使用這些框架所提供的各項功能。這在當下的開發(fā)過程中已是常態(tài),但在 Spring Boot 還沒有誕生之前卻是不可想象的。如果我們使用傳統(tǒng)的 Spring 框架,那就需要添加各種繁雜的配置信息才能啟動容器。那么,Spring Boot 是通過什么樣的機制來做到這一點的呢?這就是今天我們要討論的內(nèi)容——Spring Boot 的自動配置機制。
可以說,Spring Boot 的自動配置機制應用非常廣泛。在目前主流的開源框架中,都提供了各自的 starter 組件。例如,MyBatis 的 starter 組件為 mybatis-spring-boot-starter。而從擴展性上講,這也是 Spring Boot 為開發(fā)人員提供的一整套擴展機制,我們可以基于這套擴展機制實現(xiàn)自定義的 starter 組件。
Spring Boot 自動配置機制原理
Spring Boot 的自動配置功能非常強大,但也有一定的復雜度,讓我們先來深入理解其背后的實現(xiàn)原理。
@EnableAutoConfiguration 注解
我們通過查看@SpringBootApplication 注解的定義,發(fā)現(xiàn)該注解實際上是一個復合注解,由@SpringBootConfiguration、@ComponentScan 和@EnableAutoConfiguration 這三個獨立注解所組成。
圖 1 @SpringBootApplication 注解的組成結(jié)構(gòu)
我們知道@ComponentScan 是傳統(tǒng) Spring 框架中對內(nèi)置的注解,而@SpringBootConfiguration 注解也很簡單,實際上只是對 Spring 框架中另一個常用注解@Configuration 的一種包裝,本身沒有定義任何內(nèi)容。所以,我們接下來重點剖析@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 {};
}
可以看到,這里出現(xiàn)了一個新的注解,即@AutoConfigurationPackage。從命名上講, @AutoConfigurationPackage 注解的作用就是自動對某一個代碼包進行配置。
另一方面,我們還看到這里通過@Import 注解引入了一個 AutoConfigurationImportSelector 類。從命名上,我們也不難理解該類的作用是完成對導入的配置信息的自動選擇。AutoConfigurationImportSelector 類的核心方法 getCandidateConfigurations 實現(xiàn)了這一目標。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
…
return configurations;
}
這里引出了在 Spring Boot 中真正負責加載配置信息的 SpringFactoriesLoader 類。這些類之間的交互關系如圖 2 所示:
圖 2 AutoConfigurationImportSelector 類層結(jié)構(gòu)圖
顯然,想要完成配置信息的自動選擇,我們首先需要執(zhí)行配置文件的加載操作,這部分功能是由 SpringFactoriesLoader 來完成的。SpringFactoriesLoader 也是 Spring Boot 自動配置得以實現(xiàn)的關鍵組件,我們來一起看一下。
SpringFactoriesLoader
SpringFactoriesLoader 類似 JDK 實現(xiàn) SPI 機制時所使用的 ServiceLoader 類,區(qū)別只是配置文件的存放位置和配置項對應的鍵值定義不同。在 SpringFactoriesLoader 中,我們需要通過 META-INF/spring.factories 文件目錄,來獲取服務定義文件,并通過 EnableAutoConfiguration 這個配置鍵來獲取具體的配置信息。圖 3 展示了 SpringFactoriesLoader 和 ServiceLoader 之間的區(qū)別。
圖 3 SpringFactoriesLoader 和 ServiceLoader 區(qū)別示意圖
圖 3 SpringFactoriesLoader 和 ServiceLoader 區(qū)別示意圖
SpringFactoriesLoader 基于圖 3 指定的配置文件名和配置鍵獲取對應的配置信息,然后基于這些配置信息來實例化配置類,這里 Spring Boot 用到的是反射機制。SpringFactoriesLoader 類中的 loadSpringFactories 方法展示了這一過程。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//從緩存中獲取配置內(nèi)容,如果存在則直接返回
try {
//基于 ClassLoader 從 META-INF/spring.factories 獲取配置文件資源地址 URL
while (urls.hasMoreElements()) {
//獲取配置文件資源,加載配置項
for (Map.Entry<?, ?> entry : properties.entrySet()) {
//組裝配置項 Key-Value
}
}
//把配置信息放入緩存
//返回結(jié)果
}
}
同時,我們在 spring-boot-autoconfigure 工程的 spring.factories 配置文件中找到了如下所示配置項。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoCnotallow=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
…
可以看到,EnableAutoConfiguration 配置項中,定義了各種以-AutoConfiguration 結(jié)尾的配置類。通過 SpringFactoriesLoader,Spring Boot 就能做到在服務啟動的時候把它們加載到容器中并實現(xiàn)自動化配置。
MyBatis Spring Boot Starter
介紹完 Spring Boot 中應用程序的自動配置機制之后,我們來做一些實踐,通過剖析 MyBatis Spring Boot Starter 的啟動過程來加深對內(nèi)容的理解。
在 mybatis-spring-boot-starter 中存在幾個代碼工程,我們重點關注 mybatis-spring-boot-autoconfigure 工程。而在這個代碼工程中,最重要的顯然就是 MybatisAutoConfiguration 類。對于 Spring Boot 中的 AutoConfiguration 類,我們首先需要重點關注的是類定義上的注解。
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {
我們看到這里用到了@ConditionalOnClass 和@ConditionalOnSingleCandidate 這兩個注解,它們就是 Spring Boot 中的條件注解。在介紹 MybatisAutoConfiguration 之前,有必要對這些注解做一定展開。
@ConditionalOn 系列條件注解
我們在前面的內(nèi)容中已經(jīng)了解到以-AutoConfiguration 結(jié)尾的自動配置類數(shù)量會很多,在一個應用程序的開發(fā)過程中,我們通常不會全部用到它們。這時候就需要引入一種機制來對這些自動配置類進行過濾。為此,Spring Boot 提供了一組@ConditionalOn 系列條件注解。通過這些注解,我們就可以基于特定的條件有選擇性地加載某些配置類。在 Spring Boot 中常見的條件注解可以參考圖 4。
圖 4 常見@ConditionalOn 系列注解及其作用
在前面介紹的 MybatisAutoConfiguration 類的時候,出現(xiàn)了@ConditionalOnClass 和@ConditionalOnSingleCandidate 這兩個條件注解。基于這兩個條件注解,我們可以明確 MybatisAutoConfiguration 能夠?qū)嵗那疤嵊袃蓚€:一是類路徑中存在 SqlSessionFactory 和 SqlSessionFactoryBean;另一個則是容器中只存在一個 DataSource 實例。兩者缺一不可,這是一種常用的自動配置控制技巧。
然后,我們在 MybatisAutoConfiguration 類上看到了一個@EnableConfigurationProperties 注解。通過這個注解,所有添加了@ConfigurationProperties 注解的配置類就會自動生效。這里的@EnableConfigurationProperties 注解中指定的是 MybatisProperties 類,該類定義了 MyBatis 運行時所需要的各種配置信息,而我們在 MybatisProperties 類上確實也發(fā)現(xiàn)了@ConfigurationProperties 注解,并設置了 prefix 為“mybatis”。
@ConfigurationProperties(
prefix = "mybatis"
)
public class MybatisProperties {
...
}
最后,在 MybatisAutoConfiguration 類上還存在一個@AutoConfigureAfter 注解,這個注解可以根據(jù)字面意思進行理解,即在完成某一個類的自動配置之后再執(zhí)行當前類的自動配置。這個需要提前裝配的類指的就是 DataSourceAutoConfiguration。
MybatisAutoConfiguration
理解了@ConditionalOnXXX、@EnableConfigurationProperties 和@AutoConfigureAfter 等一系列注解之后,我們回過頭來再看 MybatisAutoConfiguration 類的代碼結(jié)構(gòu)就顯得比較簡單了。MybatisAutoConfiguration 類中的一個核心方法就是如下所示的 sqlSessionFactory 方法。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
//省略一系列配置項設置方法
return factory.getObject();
}
顯然,這里基于前面介紹的 SqlSessionFactoryBean 構(gòu)建了 SqlSessionFactory 實例。注意到在該方法上同樣添加了一個@ConditionalOnMissingBean 注解,標明只有在當前上下文中不存在 SqlSessionFactoryBean 對象時才會執(zhí)行上述方法。
同樣,添加了@ConditionalOnMissingBean 注解的,還有如下所示的 sqlSessionTemplate 方法。
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
該方法用于構(gòu)建一個 SqlSessionTemplate 對象實例。在 MyBatis 中,SqlSessionTemplate 實現(xiàn)了 SqlSession 接口,相當于是全局唯一的 SqlSession 實例。
接下來,我們需要在 META-INF/spring.factories 文件中明確所指定的自動配置類。根據(jù) Spring Boot 自動配置機制的原理,對于 mybatis-spring-boot-autoconfigure 工程而言,這個配置項內(nèi)容應該如下所示。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoCnotallow=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
至此,整個 MyBatis Spring Boot Starter 的介紹就告一段落。作為總結(jié),我們可以把創(chuàng)建一個 Spring Boot Starter 的過程抽象成三個步驟。
圖 5 創(chuàng)建 Spring Boot Starter 的三個步驟
在日常開發(fā)過程中,我們就可以基于這三大步驟來實現(xiàn)一個自定義的 Spring Boot Starter。
總結(jié)
今天,我們詳細闡述了 Spring Boot 自動配置機制的實現(xiàn)原理,從源碼角度分析了為什么 Spring Boot 能夠做到自動配置,并結(jié)合 MyBatis 框架分析了它在開源框架中的具體應用。同時,我們在本講結(jié)尾部分還總結(jié)了開發(fā)一個 Spring Boot Starter 的三大步驟。實現(xiàn)一個自定義 Spring Boot Starter 也是日常開發(fā)的常見需求,我們在開發(fā)過程中可以基于本講的內(nèi)容加深對其實現(xiàn)原理的理解。
最后,我想給你留一道思考題:你能簡要描述實現(xiàn)一個自定義 Spring Boot Starter 需要哪些開發(fā)步驟嗎?