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則會採用默認值。

由於數據源的實現有多種

  1. com.zaxxer.hikari.HikariDataSource
  2. org.apache.tomcat.jdbc.pool.DataSource
  3. 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的靜態內部類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章