SpringBoot数据库配置源码解析:自动配置注解解析
yuyutoo 2024-10-12 00:48 9 浏览 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数据库配置源码解析:自动配置注解解析
- 下篇文章给大家讲解的是SpringBoot数据库配置源码解析:自动配置内部实现解析;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
相关推荐
- ETCD 故障恢复(etc常见故障)
-
概述Kubernetes集群外部ETCD节点故障,导致kube-apiserver无法启动。...
- 在Ubuntu 16.04 LTS服务器上安装FreeRADIUS和Daloradius的方法
-
FreeRADIUS为AAARadiusLinux下开源解决方案,DaloRadius为图形化web管理工具。...
- 如何排查服务器被黑客入侵的迹象(黑客 抓取服务器数据)
-
---排查服务器是否被黑客入侵需要系统性地检查多个关键点,以下是一份详细的排查指南,包含具体命令、工具和应对策略:---###**一、快速初步检查**####1.**检查异常登录记录**...
- 使用 Fail Ban 日志分析 SSH 攻击行为
-
通过分析`fail2ban`日志可以识别和应对SSH暴力破解等攻击行为。以下是详细的操作流程和关键分析方法:---###**一、Fail2ban日志位置**Fail2ban的日志路径因系统配置...
- 《5 个实用技巧,提升你的服务器安全性,避免被黑客盯上!》
-
服务器的安全性至关重要,特别是在如今网络攻击频繁的情况下。如果你的服务器存在漏洞,黑客可能会利用这些漏洞进行攻击,甚至窃取数据。今天我们就来聊聊5个实用技巧,帮助你提升服务器的安全性,让你的系统更...
- 聊聊Spring AI Alibaba的YuQueDocumentReader
-
序本文主要研究一下SpringAIAlibaba的YuQueDocumentReaderYuQueDocumentReader...
- Mac Docker环境,利用Canal实现MySQL同步ES
-
Canal的使用使用docker环境安装mysql、canal、elasticsearch,基于binlog利用canal实现mysql的数据同步到elasticsearch中,并在springboo...
- RustDesk:开源远程控制工具的技术架构与全场景部署实战
-
一、开源远程控制领域的革新者1.1行业痛点与解决方案...
- 长安汽车一代CS75Plus2020款安装高德地图7.5
-
不用破解原车机,一代CS75Plus2020款,安装车机版高德地图7.5,有红绿灯读秒!废话不多讲,安装步骤如下:一、在拨号状态输入:在电话拨号界面,输入:*#518200#*(进入安卓设置界面,...
- Zookeeper使用详解之常见操作篇(zookeeper ui)
-
一、Zookeeper的数据结构对于ZooKeeper而言,其存储结构类似于文件系统,也是一个树形目录服务,并通过Key-Value键值对的形式进行数据存储。其中,Key由斜线间隔的路径元素构成。对...
- zk源码—4.会话的实现原理一(会话层的基本功能是什么)
-
大纲1.创建会话...
- Zookeeper 可观测性最佳实践(zookeeper能够确保)
-
Zookeeper介绍ZooKeeper是一个开源的分布式协调服务,用于管理和协调分布式系统中的节点。它提供了一种高效、可靠的方式来解决分布式系统中的常见问题,如数据同步、配置管理、命名服务和集群...
- 服务器密码错误被锁定怎么解决(服务器密码错几次锁)
-
#服务器密码错误被锁定解决方案当服务器因多次密码错误导致账户被锁定时,可以按照以下步骤进行排查和解决:##一、确认锁定状态###1.检查账户锁定状态(Linux)```bash#查看账户锁定...
- zk基础—4.zk实现分布式功能(分布式zk的使用)
-
大纲1.zk实现数据发布订阅...
- 《死神魂魄觉醒》卡死问题终极解决方案:从原理到实战的深度解析
-
在《死神魂魄觉醒》的斩魄刀交锋中,游戏卡死犹如突现的虚圈屏障,阻断玩家与尸魂界的连接。本文将从技术架构、解决方案、预防策略三个维度,深度剖析卡死问题的成因与应对之策,助力玩家突破次元壁障,畅享灵魂共鸣...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
-
- ETCD 故障恢复(etc常见故障)
- 在Ubuntu 16.04 LTS服务器上安装FreeRADIUS和Daloradius的方法
- 如何排查服务器被黑客入侵的迹象(黑客 抓取服务器数据)
- 使用 Fail Ban 日志分析 SSH 攻击行为
- 《5 个实用技巧,提升你的服务器安全性,避免被黑客盯上!》
- 聊聊Spring AI Alibaba的YuQueDocumentReader
- Mac Docker环境,利用Canal实现MySQL同步ES
- RustDesk:开源远程控制工具的技术架构与全场景部署实战
- 长安汽车一代CS75Plus2020款安装高德地图7.5
- Zookeeper使用详解之常见操作篇(zookeeper ui)
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)