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

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

yuyutoo 2024-10-12 00:48 2 浏览 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. 感谢大家的支持!

相关推荐

jQuery VS AngularJS 你更钟爱哪个?

在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...

Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0

在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...

如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例

要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...

编程技巧:Jquery实时验证,指定长度的「负小数」

为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...

一篇文章带你用jquery mobile设计颜色拾取器

【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...

编程技巧:Jquery实时验证,指定长度的「正小数」

为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...

jquery.validate检查数组全部验证

问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...

Vue进阶(幺叁肆):npm查看包版本信息

第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...

layui中使用lay-verify进行条件校验

一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...

jQuery是什么?如何使用? jquery是什么功能组件

jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...

django框架的表单form的理解和用法-9

表单呈现...

jquery对上传文件的检测判断 jquery实现文件上传

总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...

Nodejs之MEAN栈开发(四)-- form验证及图片上传

这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...

大数据开发基础之JAVA jquery 大数据java实战

上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...

推荐四个开源的jQuery可视化表单设计器

jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...

取消回复欢迎 发表评论: