SpringBoot数据库配置源码解析:自动配置注解解析

1496 篇文章 10 订阅
1494 篇文章 14 订阅

SpringBoot数据库配置源码解析

Spring Boot 对主流的数据库都提供了很好的支持,打开 Spring Boot 项目中的 starters 会发现针对 data 提供了 15 个 starter 的支持,包含了大量的关系型数据库和非关系数据库的数据访问解决方案。而本章重点关注 Spring Boot 中数据源自动配置源码的实现,及核心配置类
DataSourceAutoConfiguration 和 Jdbc TemplateAutoConfiguration 等的用法。

自动配置注解解析

首先,我们以数据源的自动配置进行讲解,数据源的自动配置像其他自动配置一样,在META-INF/spring.factories 文件中注册了对应自动配置类。

#自动配置
org. springframework. boot . autoconfigure . EnableAutoConfiguration=\
org . springframework. boot . autoconfigure. jdbc . DataSourceAutoConfiguration, \

下面我们通过分析
DataSourceAutoConfiguration 类的源代码来学习数据库自动配置的机制。先看注解部分。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource. class, EmbeddedDatabaseType . class })
@EnableConfigurationProperties (DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializat ionConf iguration.class })
public class DataSourceAutoConfiguration {
}

注解@ConditionalOnClass 要求类 路径下必须 有 DataSource 和 EmbeddedDatabaseType 类的存在。@
EnableConfigurationProperties 属性会装配 DataSourceProperties 类,该配置类与 application.properties 中的配置相对应。

比如,对于数据库我们经常在 application.properties 中做如 下的配置。

spring. datasource. url=spring . datasource . password=在 DataSourceProperties 类中都有对应的属性存在。

@ConfigurationProperties(prefix = "spring . datasource" )
public class DataSourceProperties implements BeanClassLoaderAware, Initiali
zingBean {
private String url;
private String username ;
private String password;
}
}

@lmport 注解引入了两个自动配置类
DataSourcePoolMetadataProvidersConfiguration和DataSourcelnitializationConfiguration。

配置类
DataSourcePoolMetadataProvidersConfiguration 中定义了 3 个静态内部类,用于定义3个DataSource的 DataSourcePoolMetadataProvider的初始化条件。其中包括tomcat的 DataSource、HikariDataSource 和 BasicDataSource。

我们以 tomcat 的 DataSource 为例,看一下源代码。

@Configuration(proxyBeanMethods = false)
public class DataSourcePoolMetadataProvidersConfiguration {
@Configuration( proxyBeanMethods = false)
@Condit ional0nClass(org. apache. tomcat . jdbc . pool . DataSource . class)
static class TomcatDataSourcePoolMetadataProviderConfiguration {
@Bean
public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvi
der() {
return (dataSource) -> {
org. apache . tomcat. jdbc . pool. DataSource tomcatDataSource = Data-
SourceUnwrapper
. unwrap(dataSource, org. apache . tomcat . jdbc . pool . DataSource. class);
f (tomcatDataSource != nu1l)
return new TomcatDataSourcePoolMetadata( tomcatDataSource);
return null;
}
}
}
}

内部类中判断 classpath 是否存在 tomcat 的 DataSource,如果存在,则实例化并注册一-个
DataSourcePoolMetadataProvider,其中Lambda表达式为DataSourcePoolMeta-dataProvider 的 getDataSourcePoolMetadata 方法的具体实现。DataSourcePoolMetadataProvider 的作用是基于 DataSource 提供一个DataSourcePoolMeta-data,该接口只提供了一一个对应的方法。

@FunctionalInterface
public interface DataSourcePoolMet adataProvider
//返回一个用于管理 dataSource 的 DataSourcePoolMetadata 实例,如果无法处理指定的
数据源,则返@null
DataSourcePoolMetadata getDataSourcePoolMetadata (DataSource dataSource);
}

下面我们再来看自动配置代码中
DataSourcePoolMetadataProvider 接口方法的实现逻辑。

首先,通过 DataSourceUnwrapper 的 unwrap 方法获得- 一个 DataSource 数据源;然后判断数据源是否为 null,如果不为 null,则返回一个
TomcatDataSourcePoolMetadata 对象,如果为 null,则返回 null。

DataSourceUnwrapper 类 的 主 要 作 用 是 提 取 被 代 理 或 包 装 在 自 定 义 Wrapper ( 如Delegating-DataSource )中的数据源。DataSourceUnwrapper 的 unwrap 方法部分代码实现如下。

public static <T> T unwrap(DataSource dataSource, Class<T> target) {
//检查 DataSource 是否能够转化为目标对象,如果可以转化返回对象
if (target . isInstance(dataSource)) {
return target . cast(dataSource);
/检查包装 Wrapper 是否为 DataSource 的包装类, 如果是则返回 DataSource, 否则返
@null
T unwrapped = safeUnwrap(dataSource, target);
if (unwrapped != nu1l) {
return unwrapped;
//判断 elegatingDataSource 是 否存在
if (DELEGATING_ DATA_ SOURCE_ PRESENT) {
DataSource targetDataSource = DelegatingDataSourceUnwrapper. getTarget-
DataSource(dataSource);
if (targetDataSource != null) {
//递归调用本方法
return unwrap(targetDataSource, target);
//代理判断处理
if (AopUtils. isAopProxy(dataSource)) {
Object proxyTarget = AopProxyUtils . getSingletonTarget (dataSource);
if (proxyTarget instanceof DataSource)return unwrap( (DataSource) proxyTarget, target);
return null;
}

可以看出 unwrap 方法支持以下形式的检查。

.直接检查对象与目标是否符合。

.包装类的检查(DataSource 本身继承了 Wrapper 接口)。

.判断 DelegatingDataSource 类型的数据源是否存在,如果存在则递归调用 umwrap 方法。

.检查 DataSource 是否被代理的对象。

如果符合上面检查条件(按照先后顺序),则根据不同的情况通过不同的方式获得DataSource 对象并返回。

当获取 DataSource 对象之后便直接创建
TomcatDataSourcePoolMetadata 类的对象,该类是针对 Tomcat 数据源的 DataSourcePoolMetadata 具体实现的。

在创建对象时会将传入的 DataSource 对象赋值给
TomcatDataSourcePoolMetadata 的抽象父类 AbstractDataSourcePoolMetadata 的成员变量。

public abstract class AbstractDataSourcePoolMetadata<T extends DataSourEim-
plements DataSourcePoolMetadata {
private final T dataSource;
protected AbstractDataSourcePoolMetadata(T dataSource) {
this. dataSource = dataSource;
}

针对 DataSourcePoolMetadata 接口方法的具体实现,都是围绕着 DataSource 对象中存储的数据源信息展开的。

DataSourcePoolMetadata 接口提供了大多数数据库都提供的元数据的方法定义。

public interface DataSourcePoolMetadata {
//返回当前数据库连接他的情况,返回值在 0 至 1 之间(如果连接他没有限制,值为-1)
//返回值 1 表示:已分配最大连接数
//返回值 0 表示:当前没 有连接处于活跃犹态
//返回值- 1 表示:可以分配的连接数没有限制
//返回 null 表示:当前数据源不提供必要信息进行计算
Float getUsage();
//返回当前已分配的活跃连接数,返回 null, 则表示该信息不可用
Integer getActive();
//返回同时可分配的最大活跃连接数,返@-1 表示不限制,返@null 表示该信息不可用
Integer getMax();
//返回连接地中最小空闲连接数,返@null 表示该信息不可用Integer getMin();
//返回查询以验证连接是否有效,返@null 表示该信息不可用
String getValidat ionQuery();
连接他创建的连接的默认自动提交状态。如果未将其值没为 null,则默认采用 JDBC 驱动
//如果设置为 null, 则方法 java. sql . Connect ion. setAutoCommit(boolean)将不会被调用
Boolean getDefaultAutoCommit();
}

以 DataSourcePoolMetadata 的 getUsage 方法为例,我们看一下具体实现方式。该方法在其子类
AbstractDataSourcePoolMetadata 中实现。

@Override
public Float getUsage() {
Integer maxSize = getMax();
Integer currentSize = getActive();
//数据源不支持该信息
if (maxSize == null|I currentSize == nu1l) {
return null;
//分配连接没有限制
if (maxSize < 0) {
return -1F;
//当前没有活跃连接
if (currentSize == 0) {
return OF ;
/计算 currentSize maxSize 比值
return (float) currentSize / (float) maxSize;
}

getUsage 方法的实现逻辑很清晰,首先通过接口中其他方法来获取数据并进行判断。如果获取 maxSize 或 currentSize 为 null, 说明该数据源不支持该信息。如果 maxSize 小于 0,则表示分配连接没有限制;如果 currentSize 等 于 0,则表示当前没有活跃连接;其他情况则计算 currentSize 和 maxSize 比值。

还是上面提到的,这些信息源于构建 NatoCaiirnnDnlMntodnto 传入的 DataSource。

讲解完了
DataSourcePoolMetadataProvidersConfiguration,下面再看另外-个引入的配置类 DataSourcelnitializationConfiguration,它主要的功能是配置数据源的初始化。


DataSourcelnitializationConfiguration 同样分两部分:注解引入和内部实现。

@Configuration(proxyBeanMethods = false)
@Import({ DataSourceInitializerInvoker . class, DataSourceInitializationConfiration. Registrar .class })
class DataSourceInitializat ionConfiguration {
}

首 先 看 引 入 的
DataSourcelnitializerlnvoker, 该 类 实 现 了 ApplicationListener 接 口 和Initiali-zingBean 接口,也就是说,它同时具有事件监听和执行自定义初始化的功能。

class DataSourceInitializer Invoker implements ApplicationL istener <DataSourc
SchemaCreatedEvent>, InitializingBean
DataSourceInitializer Invoker (objectProvider<DataSource> dataSource, DataS
Properties properties,
ApplicationContext applicationContext) {
this . dataSource = dataSource;
this . properties = properties;
this . applicationContext = applicat ionContext;
}


DataSourcelnitializerInvoker 构 造 方 法 被 调 用 时 会 传 入 数 据 源 、 数 据 源 配 置 和Application-Context 信息,并赋值给对应的属性。

由于
DataSourcelnitializerlnvoker 实现了 InitializingBean 接口,当 BeanFactory 设置完属性之后,会调用 afterPropertiesSet 方法来完成自定义操作。

@Override
public void afterPropertiesSet() {
//获取 DataSourceInitializer, 基 FDataSourceProperties 初始化 DataSource
DataSourceInitializer initializer = getDataSourceInitializer();
if (initializer != null) {
//执行 DDL 语句(schema-*. sql)
boolean schemaCreated = tisaorcntiieri cretschema();
// 初始化操作
initialize(initializer);
private void initialize(DataSourceInitializer initializer) {
this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(i
nitializer.getDataSource()));
// 此时,监昕器可能尚未注册,不能完全依赖,因此主动澜用
if (!this.initialized)
this . dataSourceInitializer. initSchema();
this.initialized = true;
} catch (IllegalStateException ex) {
}

在 afterPropertiesSet 中重点做了 DataSourcelnitializer 的实例化和初始化操作。其中DataSourceInitializer 的实例化比较简单,就是根据数据源、配置属性和 ApplicationContext创建了一个对象,并将对象赋值给
DataSourcelnitializerlnvoker 的属性,具体实现如下。

private DataSourceInitializer getDataSourceInitializer() {
if (this.dataSourceInitializer == null;
DataSource ds = this . dataSource . getIfUnique();
this. dataSourceInitializer = new DataSourceInitializer(ds, this. prope
rties ,
this. applicati
onContext);
return this . dataSourceInitializer;
}

完成了 DataSourcelnitializer 的初始化,后续的操作便是调用其提供的方法进行初始化操作。

比如上面代码中调用 createSchema 方法来执行 DDL 语句(schema-* .sql)就是进行初始化操作。

值得注意的是,afterPropertiesSet 方 法中还调用了 initialize 方法,initialize 方法中首先发布了一个
DataSourceSchemaCreatedEvent 事件。然后,为了防止在发布事件时对应的监听并未注册,在发布完事件之后,主动做了监听事件中要做的事。

而对应的监听事件,同样定义在
DataSourcelnitializerlnvoker 类中,上面我们已经得知它实现了 ApplicationListener 接口,监听的便是上面发布的事件。onApplicationEvent 方法中的实现与 initialize 方法的实现基本相同(除了发布事件操作)。

@Override
public void onApplicationEvent (DataSourceSchemaCreatedEvent event) {
//事件可能发生多次,这里未使用数据源事件
DataSourceInitializer initializer = getDataSourceInitializer();
if (!this. initialized && initializer != null) {
initializer . initSchema();
this. initialized = true;
}

这里稍微拓展一下 DataSourcelnitializer 的两个方法 createSchema 和 initSchema,先看源代码。

boolean createSchema() {
List<Resource> scripts = getScripts("spring . datasource . schema", this. prop
erties.
getSchema(), "schema");
if (!scripts. isEmpty()) {if (lisEnabled()) {
"Initialization disabled (not running DDL scripts)");
return false;
String username = this . properties . getSchemaUsername() ;
String password = this . properties . getSchemaPassword();
runScripts(scripts, username, password);
return !scripts. isEmpty();
void initSchema() {
List<Resource>
scripts = getScripts("spring. datasource . data", this . proper
getData(), "data");
//省略部分与 createSchema 方法-致

createSchema 方法和 initSchema 方法都是获取指定位置或类路径中的 SQL (.sq) 文件,然后再获得用户名和密码,最后执行 SQL 文件中的脚本。

这两个方法不同之处在于:createSchema 常用于初始化建表语句;

initSchema 常用于插入数据及更新数据操作。

在方法中也可以看到,可在 application.properties 文件 中进行如下配置来指定其 SQL 文件位置。

spring . datasource . schema=classpath:schema -my-mysq1.sql
spring . datasource . data=classpath:data-my-mysql . sql

也 就 是 说 , 可 以 通 过
DataSourcelnitializationConfiguration 引 入 的DataSourcelnitializer-Invoker 来完成数据库相关的初始化操作。

下面我们再看
DataSourcelnitializationConfiguration 引入的另外-一个内部类 Registrar,这也是该自动配置类唯一的代码实现。

static class Registrar implements ImportBeanDefinitionRegistrar {
private static final String BEAN NAME = "dataSourceInitializerPostProcess
or" ;
@Override
public void registerBeanDefinit ions (Annotat ionMetadata importingClassMeta
ata,
BeanDefinitionRegistry registry) {
if (!registry. containsBeanDefinition(BEAN NAME)) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition. setBeanClass (DataSourceInitializerPostProcessor .class)
beanDef inition. setRole(BeanDef inition. ROLE_ INFRASTRUCTURE);
beanDefinition. setSynthetic(true);registry. registerBeanDefinition(BEAN_ NAME, beanDefinition);
}
}
}

内 部 类 Registrar 通 过 实 现
ImportBeanDefinitionRegistrar 接 口 来 完 成DataSourcelnitiali-zerPostProcessor 的注册。关于通过 ImportBeanDefinitionRegistrar 动态注入 Bean 的具体使用方法,我们在上一章节中已经讲过,这里不再赘述,下面主要看 一下实现逻辑。

在 registerBeanDefinitions 方法中首先判断名称为
dataSourcelnitializerPostProcessor 的Bean 是否已经被注册。如果未被注册,则通过创建 GenericBeanDefinition 对象封装需要动态创建 Bean 的信息,然后通过 BeanDefinitionRegistry 进行注册。

需要注意的是,这里设 置 GenericBeanDefinition 的 synthetic 属性为 true ,这是因为不需要对此对象进行后续处理,同时也避免 Bean 的级联初始化。

最 后 再 看 看 这 里 注 册 的
DataSourcelnitializerPostProcessor 的 作 用 。 它 实 现 了BeanPost-Processor 接口,用于确保 DataSource 尽快初始化 DataSourcelnitializer.class DataSourceInitializerPostProcessor implements BeanPostProcessor, Orde

@Override
public int getOrder() {
return Ordered .HIGHEST_ PRECEDENCE + 1;
@Override
public object postProcessAfterInitialization(object bean, String beanName
throws BeansException {
if (bean instanceof DataSource) {
/遇到 DataSource 便初始化 DataSourceInitial izerInvoker
this . beanF actory. getBean(DataSourceInitializerInvoker.class);
return bean;
}

以上代码主要实现了两个功能,一个是将该类的优先级设置为仅次于最高优先级(通过 order加1),另 一个是
postProcessAfterlnitialization中进行Bean类型的判断,如果为DataSource类型,则通过 BeanFactory 初始化 DataSourcelnitializerlnvoker 的 Bean 对象,然后返回。

这样处理是为了尽快初始化
DataSourcelnitializerlnvoker 的对象。

至此,关于自动配置类
DataSourceAutoConfiguration 注解部分的相关功能已经讲解完毕,下节我们继续学习其内部实现。

本文给大家讲解的内容是SpringBoot数据库配置源码解析:自动配置注解解析

  1. 下篇文章给大家讲解的是SpringBoot数据库配置源码解析:自动配置内部实现解析;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值