自動裝配原理分析
條件註冊機制
spring-context模塊中有兩個組件:Condition
接口和@Conditional
註解,在@Conditional
註解中可以指定一組Condition
實現, 通常@Conditional
是和@Component
、@Configuration
和@Bean
搭配使用的,當用在@Configuration
配置類上 則對其中的所有@Bean
方法以及@Import
導入的所有配置類都有效。
Condition
接口表示執行條件測試,它有一個matches
方法,方法返回true
時條件測試通過,只有@Conditional
中 給出的所有條件測試通過後纔會註冊bean。
Spring boot項目的 spring-boot-autoconfigure 模塊裏面寫了很多種@ConditionalOn...
註解,每一個這種 註解本質就是@Conditional
註解的使用,每一個註解表示一組需要測試的條件,例如常見的有:
@ConditionalOnClass
測試classpath中存在指定的類型則生效。@ConditionalOnClass
測試classpath種不存在指定的類型則生效。@ConditionalOnBean
測試容器中已經註冊了某bean,則生效。@ConditionalOnMissingBean
測試容器種不存在某bean,則生效。ConditionalOnProperty
測試Environment中存在指定的屬性,則生效。@ConditionalOnWebApplication
測試應用程序是否是一個web應用程序。
還有很多這樣的註解,可以到...autoconfigure.condition
包下面查看。
spring-boot-autoconfigure模塊裏面還有各種...AutoConfiguration
類,它們都被特定的 @ConditionalOn...
註解修飾,是實現自動配置的關鍵,例如JdbcTemplateAutoConfiguration
等,打開它的源代碼查看一下是如何聲明自動配置的:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {
}
查看其中導入的JdbcTemplateConfiguration:
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(JdbcOperations.class)
class JdbcTemplateConfiguration {
@Bean
@Primary
JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
JdbcProperties.Template template = properties.getTemplate();
jdbcTemplate.setFetchSize(template.getFetchSize());
jdbcTemplate.setMaxRows(template.getMaxRows());
if (template.getQueryTimeout() != null) {
jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
}
return jdbcTemplate;
}
}
只有在classpath中存在DataSource.class、JdbcTemplate.class、容器中只存在一個DataSource實例、並且容器中不存在 JdbcOperation實例,這些條件滿足後纔會在容器中註冊一個JdbcTemplate實例。
總結:@ConditionalOn...
和...AutoConfiguration
是理解spring boot自動配置機制的關鍵。
工廠加載機制
理解了...AutoConfiguration
配置類的作用和效果,那這些自動配置類又是如何發生效果的呢?
背後的大致原理:其實是@EnableAutoConfiguration
的作用,@SpringBootApplication
這個總的註解導入了 @EnableAutoConfiguration
,而@EnableAutoConfiguration
通過 @Import 導入了 AutoConfigurationImportSelector
這個ImportSelector實現,AutoConfigurationImportSelector
的主要作用就是導入各種...AutoConfiguration
自動配置類,在背後它是通過Spring工廠加載機制實現加載的,這就是Spring boot程序只需要一個 @SpringBootApplication
註解,就可以啓動自動配置原因。
// 這是@EnableAutoConfiguration的源代碼概要
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {}; //要排出的XxxAutoConfiguration類
String[] excludeName() default {};
}
AutoConfigurationImportSelector
是一個 ImportSelector接口實現,跟蹤其selectImorts方法,下面看看局部重要代碼:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// ...
// 調getAutoConfigurationEntry方法查找自動配置類
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// ...
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 調getCandidateConfigurations方法加載自動配置類
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 這裏就是SpringFactoiesLoader加載的地方
// 加載的組件就是用“org.springframework.boot.autoconfigure.EnableAutoConfiguration.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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
spring.factories
Spring工廠加載機制查找組件的地址是 META-INF/spring.factories
文件,是一個properties文件,對格式有一定要求, key部分是某一類功能組件的上層抽象接口的全限定名,value是具體實現類的全限定名列表,多個實現類型用逗號分割,下面是 spring-boot-autoconfigure模塊中的例子:
# 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,\
.....
結合前面的代碼可以看到,AutoConfigurationImportSelector委託SpringFactoriesLoader以 org.springframework.boot.autoconfigure.EnableAutoConfiguration
爲key,從META-INF/spring.factories
文件中讀取 所有的自動配置組件。
Spring boot 2.7 加載邏輯有點變更
在Spring boot 2.7中,自動配置類的加載機制發生了變化,增加了一種自動配置類加載方式,下面列出的是變化的結果。
(1)自動配置類列表除了可以使用 META-INF/spring.factories
文件來維護,又增加一個 META-INF/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件維護,新的文件格式 不再是properties文件,而是每個自動配置類單獨寫一行,更像Java SPI services配置文件。
(2)Spring boot 2.7 引入一個新的註解 @AutoConfiguration
,所有的自動配置類現在要增加這個註解的標註聲明。
(3)加載方式變化,除了對 SpringFactoriesLoader
加載方式仍然保持兼容,又引入了一個ImportCandidates
組件, 專門用於加載@Import
導入的自動配置類,這個組件即從新的 META-INF/xxxConfigration.imports
文件中查找配置,這點 查看2.7版本中 AutoConfigurationImportSelector#getCandidateConfigurations
方法的實現。
保持對 SpringFactoriesLoader
加載方式保持兼容是有必要的,因爲還有除開Spring boot自身之外的第三方模塊, 已經存在的三方模塊任然是依賴於 SpringFactoriesLoader
機制在加載。
總結
XxxAutoConfiguration,@ConditionalOnXxx
@Import(ImportSelector)
@EnableXxx <=> @Import(xxxImportSelector)
具體的就是:
@SpringBootApplication <= @EnableAutoConfiguration
@EnableAutoConfiguration <=> @Import(AutoConfigurationImportSelector)
AutoConfigurationImportSelector查找所有XxxAutoConfiguration配置對象(SpringFactoriesLoader)
屬性加載原理
PropertySourceLoader
Spring boot有一個PropertySourceLoader
組件,外部配置就是通過它完成加載的,默認有兩種實現:
PropertiesPropertySourceLoader
YamlPropertySourceLoader
分別對應properties屬性配置和yml屬性配置。
那麼,PropertySourceLoader
是如何被觸發加載的呢,仍然是通過工廠加載機制實現的,看下面的ConfigFileApplicationListener
。
ConfigFileApplicationListener監聽器
ConfigFileApplicationListener
是一個spring事件監聽器,同時也是一個EnvironmentPostProcessor
,這意味着它可以修改底層的Environment
, 外部配置就是這樣被填充到Environment
管理的屬性集中的。
SpringApplication會發布事件,當在調用容器的refresh
方法前,此時容器底層的Environment
已經初始化完成,SpringApplication 會發佈一個ApplicationEnvironmentPreparedEvent事件,這個事件被ConfigFileApplicationListener
監聽到,在其實現的 onApplicationEvent(event)
方法中會加載所有EnvironmentPostProcessor
組件,而加載EnvironmentPostProcessor
是通過 SpringFactoriesLoader完成的,看看下面這幾個方法:
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
//......
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 如果是ApplicationEnvironmentPreparedEvent事件:
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
// 加載EnvironmentPostProcessor:
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
// SpringFactoriesLoader加載EnvironmentPostProcessor:
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}
//......
}
加載全部的EnvironmentPostProcessor後,就會回調 postProcessEnvironment(env, springApplication)
方法, 這其中一個就是 ConfigFileApplicationListener
的實現,在此方法中通過創建一個Loader內部類對象,最後通過 PropertySourceLoader
完成加載,在 ConfigFileApplicationListener
中,PropertySourceLoader
的所有實現 也是通過工廠加載機制創建的。