背景
俗话说万变不离其宗,代码中对数据库的操作,首先是要获取数据库连接,而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的静态内部类。