聲明
源碼基於Spring Boot 2.0.4
前文
這兩篇文章對理解這篇文章的知識會很有幫助。
自動配置介紹
在Spring Boot中開啟自動配置只需要在配置類上加上@EnableAutoConfiguration
注解即可。Spring Boot程序都會在啟動類添加@SpringBootApplication
注解,@SpringBootApplication
注解其實是是一個組合注解,相當於@Configuration
、@EnableAutoConfiguration
、@ComponentScan
這幾個注解一起使用。因此Spring Boot程序默認開啟自動配置。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
/**
* 自動配置的開關,當我們程序使用了@EnableAutoConfiguration
* 如果想要關掉自動配置,只需在application.properties文件加上
* spring.boot.enableautoconfiguration = false 或者
* spring.boot.enable-auto-configuration = false
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 想要排除哪些自動配置類
*/
Class<?>[] exclude() default {};
/**
* 同上,只不過使用類的完全限定名
*/
String[] excludeName() default {};
}
默認情況下,Spring會去尋找類路徑下META-INF/spring.factories文件,然后加載這個文件指定的自動配置類。具體自動配置行為全都是依賴這些自動配置類完成的。
# spring.factories 部分內容
# 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,\
等
源碼分析
所有的@EnableXXX
注解行為都是由@Import
注解來完成的,@EnableAutoConfiguration
注解同樣也被Import
注解注解了。重點關注AutoConfigurationImportSelector
即可。這個類實現了DeferredImportSelector
接口。也就是在主配置類全部解析完成后再執行selectImports
方法選擇哪些類繼續進行解析。也就是說主配置類定義的bean優先注冊,然后再注冊selectImports
選擇的類,保證了用戶的配置優先。
注: 主配置定義的bean包括主配置本身以及通過@import
注解引入的bean以及注解掃描注冊的bean。
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
/**
* 將選擇的類繼續交給Spring解析
* 解析類為ConfigurationClassPostProcessor
* 感興趣可參考這篇文章<<Spring 注解配置原理>>
* 鏈接: https://www.cnblogs.com/wt20/p/11823783.html
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判斷spring.boot.enable-auto-configuration屬性值
if (!isEnabled(annotationMetadata)) {
// 返回一個空數組,不選擇任何一個自動配置類,即關閉自動配置
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 獲取META-INF/spring.factories文件中
// org.springframework.boot.autoconfigure.EnableAutoConfiguration屬性值
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 移除重復自動配置類
configurations = removeDuplicates(configurations);
// 獲取exclude屬性以及excludeName屬性指定的類,將這些自動配置類跳過
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 校驗這些排除類
checkExcludedClasses(configurations, exclusions);
// 從自動配置類集合中移除排除的類
configurations.removeAll(exclusions);
// 對剩下的自動配置類做一個過濾,具體不展開了
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
// 判斷spring.boot.enable-auto-configuration屬性值, 默認為true
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
// 檢查有沒有無效的排除類
private void checkExcludedClasses(List<String> configurations,
Set<String> exclusions) {
List<String> invalidExcludes = new ArrayList<>(exclusions.size());
/*
* 如果排除類在類路徑存在並且不在自動配置類集合中就是一個無效的排除類
*/
for (String exclusion : exclusions) {
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader())
&& !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
// 檢測到指定的排除類包含無效的類,拋出異常
if (!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}
案列分析-數據源的自動配置
數據源的自動配置類為DataSourceAutoConfiguration
,我們可以在spring-boot-autoconfigure.jar中的META-INF/spring.factories文件中找到這個類的聲明。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
略,\
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,\
略,\
從類路徑中尋找具體數據源實現的順序為 HikariCP -> Tomcat -JDBC -> Commons-DBCP2 -> 其它數據源
可以參考官方文檔Working with SQL Databases - 29.1.2。
也可以在applicaiton.properties文件中直接指定 spring.datasource.type
屬性來指定使用哪個數據源,從而繞過這個尋找機制。
/*
* 只截取關鍵代碼
*/
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
/**
* 從這里就可以看出尋找順序了
* 當容器中沒有DataSource 或者 XADataSource這兩個類的bean才去創建
*/
@Configuration
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
}
}
/**
* 這里只挑Tomcat-JDBC做下說明
* HikariCP、Commons-DBCP2和Tomcat-JDBC是一樣的
*/
abstract class DataSourceConfiguration {
@SuppressWarnings("unchecked")
protected static <T> T createDataSource(DataSourceProperties properties,
Class<? extends DataSource> type) {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
/**
* 需要滿足下面三個條件
* 1. tomcat-jdbc數據源在類路徑上
* 2. 容器中不存在DataSource的bean
* 3. 如果沒有指定spring.datasource.type屬性,默認通過(matchIfMissing = true)
* 明確指定時spring.datasource.type值必須為org.apache.tomcat.jdbc.pool.DataSource
* Tomcat Pool DataSource configuration.
*/
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource", matchIfMissing = true)
static class Tomcat {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
public org.apache.tomcat.jdbc.pool.DataSource dataSource(
DataSourceProperties properties) {
org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(
properties, org.apache.tomcat.jdbc.pool.DataSource.class);
DatabaseDriver databaseDriver = DatabaseDriver
.fromJdbcUrl(properties.determineUrl());
String validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
dataSource.setTestOnBorrow(true);
dataSource.setValidationQuery(validationQuery);
}
return dataSource;
}
}
/**
* 通用的數據源配置,如果使用的數據源實現不是以上三種的一個,將會通過這個類來處理
* 當然依然要滿足條件才會配置
* 1. 容器中不存在dataSource bean
* 2. 指定了spring.datasource.type屬性,只要該屬性不為false就會通過
* 因為@ConditionalOnProperty注解的havingValue屬性沒有指定
* Generic DataSource configuration.
*/
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {
@Bean
public DataSource dataSource(DataSourceProperties properties) {
// 初始化數據源
return properties.initializeDataSourceBuilder().build();
}
}
}
// DataSourceProperties類
public DataSourceBuilder<?> initializeDataSourceBuilder() {
// 初始化屬性
// 通常而言驅動類都不用明確指定,Spring會跟據url做推斷
// url, username, passowrd如果沒有指定走內嵌數據庫
return DataSourceBuilder.create(getClassLoader()).type(getType())
.driverClassName(determineDriverClassName()).url(determineUrl())
.username(determineUsername()).password(determinePassword());
}
// 推斷driverClassName
public String determineDriverClassName() {
// 明確指定, 檢查下驅動是否可以被加載,可以直接返回
if (StringUtils.hasText(this.driverClassName)) {
Assert.state(driverClassIsLoadable(),
() -> "Cannot load driver class: " + this.driverClassName);
return this.driverClassName;
}
// 從url中推斷
String driverClassName = null;
if (StringUtils.hasText(this.url)) {
driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
}
// url也為空,走內嵌數據庫,前提是類路徑需要存在內嵌數據庫依賴
if (!StringUtils.hasText(driverClassName)) {
driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
throw new DataSourceBeanCreationException(
"Failed to determine a suitable driver class", this,
this.embeddedDatabaseConnection);
}
return driverClassName;
}