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创建的整体流程
《Spring Data之DataSource创建及源码分析》

其中PooledDataSourceCondition、PooledDataSourceAvailableCondition是DataSourceAutoConfiguration的静态内部类;Hikari是DataSourceConfiguration的静态内部类。

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/f4761/article/details/83536210
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞