百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

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

yuyutoo 2024-10-12 00:48 3 浏览 0 评论

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. 感谢大家的支持!

相关推荐

史上最全的浏览器兼容性问题和解决方案

微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...

平面设计基础知识_平面设计基础知识实验收获与总结
平面设计基础知识_平面设计基础知识实验收获与总结

CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...

2025-02-21 16:01 yuyutoo

写作排版简单三步就行-工具篇_作文排版模板

和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...

写一个2048的游戏_2048小游戏功能实现

1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...

今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……

  最近的天气暖和得让很多小伙伴们喊“热”!!!  昨天的气温到底升得有多高呢?你家有没有榜上有名?...

CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式

之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...

柠檬科技肖勃飞:大数据风控助力信用社会建设

...

你的自我界限够强大吗?_你的自我界限够强大吗英文

我的结果:A、该设立新的界限...

行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?

行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...

让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华

去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...

2025-02-21 16:00 yuyutoo

今年国家综合性消防救援队伍计划招录消防员15000名

记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...

一起盘点最新 Chrome v133 的5大主流特性 ?

1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...

竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使

style="text-align:center;"data-mce-style="text-align:...

学物理能做什么?_学物理能做什么 卢昌海

作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...

你不知道的关于这只眯眼兔的6个小秘密
你不知道的关于这只眯眼兔的6个小秘密

在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...

2025-02-21 16:00 yuyutoo

取消回复欢迎 发表评论: