背景
俗話說萬變不離其宗,代碼中對數據庫的操作,首先是要獲取數據庫連接,而Java中最原生的連接方式就是通過DriverManager
private static String driver = "org.h2.Driver";
private static String url = "jdbc:h2:mem:test";
private static String user = "sa";
@Test
public void test_conn() throws SQLException {
Connection conn = DriverManager.getConnection(url,user,"");
Assert.assertNotNull(conn);
}
在實際項目中通過DriverManager獲取連接顯然是不太合適的,因爲每次getConnection
都將與數據庫進行一次交互,而數據庫對連接的創建,用戶名密碼的校驗也將消耗一定的資源。
DataSource的作用簡單講,即維護了一組可用的Connection
,在獲取連接時可直接從其維護的連接池中獲取一個可用連接。數據源與連接池的關係是,連接池是數據源的實現手段。Spring Boot的默認數據源是Hikari DataSource,手動創建一個DataSource代碼如下
private static String url = "jdbc:h2:mem:test";
private static String user = "sa";
@Test
public void test_hikari() throws SQLException {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(user);
HikariDataSource dataSource = new HikariDataSource(config);
Connection conn = dataSource.getConnection();
Assert.assertNotNull(conn);
}
在上篇博客中我們並沒有編寫Hikari的任何代碼,但Hikari的數據源就自動創建了,這是爲什麼呢,接下來我們來分析一下。
數據源創建
Spring Boot是通過自動配置的方式來創建相關組件的,DataSource的自動配置入口類是DataSourceAutoConfiguration
@Configuration
// 當存在DataSource和EmbeddedDatabaseType類時生效
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
/**
* 數據源自動配置
*/
@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 {
}
/**
* 檢查是否配置了spring.datasource.type屬性
* 或者PooledDataSourceAvailableCondition條件爲true
*/
static class PooledDataSourceCondition extends AnyNestedCondition {
PooledDataSourceCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty(prefix = "spring.datasource", name = "type")
static class ExplicitType {
}
@Conditional(PooledDataSourceAvailableCondition.class)
static class PooledDataSourceAvailable {
}
}
/**
* 測試支持的連接池是否可用
*/
static class PooledDataSourceAvailableCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
//構建一個條件信息
ConditionMessage.Builder message = ConditionMessage
.forCondition("PooledDataSource");
//
if (getDataSourceClassLoader(context) != null) {
return ConditionOutcome
.match(message.foundExactly("supported DataSource"));
}
return ConditionOutcome
.noMatch(message.didNotFind("supported DataSource").atAll());
}
/**
* 獲取DataSource的ClassLoader
* 目的是檢查默認支持的DataSource實現類是否存在
*/
private ClassLoader getDataSourceClassLoader(ConditionContext context) {
Class<?> dataSourceClass = DataSourceBuilder
.findType(context.getClassLoader());
return (dataSourceClass != null) ? dataSourceClass.getClassLoader() : null;
}
}
}
其中PooledDataSourceCondition繼承了AnyNestedCondition,AnyNestedCondition的作用是當定義的條件中,只要有一個條件滿足則整體返回匹配結果true。
總結一下,由於我們並沒有配置spring.datasource.type,所以會繼續根據PooledDataSourceAvailableCondition的結果判斷,PooledDataSourceAvailableCondition判斷的依據爲是否能夠加載到默認的DataSource實現類,通過DataSourceBuilder.findType()
private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] {
"com.zaxxer.hikari.HikariDataSource",
"org.apache.tomcat.jdbc.pool.DataSource",
"org.apache.commons.dbcp2.BasicDataSource" };
public static Class<? extends DataSource> findType(ClassLoader classLoader) {
for (String name : DATA_SOURCE_TYPE_NAMES) {
try {
return (Class<? extends DataSource>) ClassUtils.forName(name,
classLoader);
}
catch (Exception ex) {
// Swallow and continue
}
}
return null;
}
看到findType的實現就比較清楚了,由於依賴了hikari,那麼com.zaxxer.hikari.HikariDataSource是能被正常加載到的。
到這裏PooledDataSourceCondition也就匹配通過了,通過後進一步@Import裏的操作,可以看到Import了DataSourceConfiguration.Hikari.class
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
static class Hikari {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties,
HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
@SuppressWarnings("unchecked")
protected static <T> T createDataSource(DataSourceProperties properties,
Class<? extends DataSource> type) {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
這裏通過@ConfigurationProperties(prefix = "spring.datasource.hikari")會讀取application.properties中spring.datasource.hikari開頭的相關配置到DataSourceProperties
中,供createDataSource使用,如果沒有設置,hikari則會採用默認值。
由於數據源的實現有多種
- com.zaxxer.hikari.HikariDataSource
- org.apache.tomcat.jdbc.pool.DataSource
- org.apache.commons.dbcp2.BasicDataSource
但他們都是繼承自javax.sql.DataSource
有通用的接口,所以在DataSourceBuilder的build中可以使用工具類直接創建
public T build() {
Class<? extends DataSource> type = getType();
DataSource result = BeanUtils.instantiateClass(type);
maybeGetDriverClassName();
bind(result);
return (T) result;
}
根據方法的調用路徑,這裏返回的result會返回到@Bean
註解的dataSource方法,那麼Spring Context中就有了HikariDataSource實例,在後續的很多方法中需要注入DataSource時,也就有了來源。
最後貼一張整個邏輯的時序圖,以便了解DataSource創建的整體流程
其中PooledDataSourceCondition、PooledDataSourceAvailableCondition是DataSourceAutoConfiguration的靜態內部類;Hikari是DataSourceConfiguration的靜態內部類。