Spring Data之DataSource建立及原始碼分析
背景
俗話說萬變不離其宗,程式碼中對資料庫的操作,首先是要獲取資料庫連線,而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的靜態內部類。