Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(下)

在上一篇博客《Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘》介绍了dynamic-datasource-spring-boot-starter的自动配置类和配置属性类之后,本文继续来剖析多数据源是如何切换的,什么时候切换的。

前文中提到dynamic-datasource-spring-boot-starter的自动配置类DynamicDataSourceAutoConfiguration在初始化的时候,会创建一个Advisor bean。

	@Role(2)@Beanpublic Advisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(this.properties.isAllowedPublicOnly(), dsProcessor);DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);advisor.setOrder(this.properties.getOrder());return advisor;}

该bean负责处理带有@DS注解的类或方法。它发挥的作用和通过@Pointcut+@Before或@Around发挥的作用一致。更多细节可见《探索微服务中的权限控制:一次线上问题排查的思考》

public class DynamicDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {private final Advice advice;private final Pointcut pointcut;public DynamicDataSourceAnnotationAdvisor(@NonNull DynamicDataSourceAnnotationInterceptor dynamicDataSourceAnnotationInterceptor) {// 初始化的时候,传入了dynamicDataSourceAnnotationInterceptor// 说明拦截器发挥了通知的作用。this.advice = dynamicDataSourceAnnotationInterceptor;this.pointcut = buildPointcut();}// 核心方法, 切点获取,类比@Pointcut注解 @Overridepublic Pointcut getPointcut() {return this.pointcut;}// 核心方法,通知获取, 类比@Before, @After, @Around注解@Overridepublic Advice getAdvice() {return this.advice;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {if (this.advice instanceof BeanFactoryAware) {((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);}}private Pointcut buildPointcut() {Pointcut cpc = new AnnotationMatchingPointcut(DS.class, true);Pointcut mpc = new AnnotationMethodPoint(DS.class);return new ComposablePointcut(cpc).union(mpc);}……
}

DynamicDataSourceAnnotationAdvisor类在初始化的时候,传入了DynamicDataSourceAnnotationInterceptor类型的拦截器作为advice, 说明该拦截器发挥了通知的作用(类似@Before, @Around)。

public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {/*** The identification of SPEL.*/private static final String DYNAMIC_PREFIX = "#";private final DataSourceClassResolver dataSourceClassResolver;private final DsProcessor dsProcessor;public DynamicDataSourceAnnotationInterceptor(Boolean allowedPublicOnly, DsProcessor dsProcessor) {dataSourceClassResolver = new DataSourceClassResolver(allowedPublicOnly);this.dsProcessor = dsProcessor;}// 核心方法, dsKey就是@DS注解中的值, 如@DS("cusOB"),那么dsKey = "cusOB"@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {String dsKey = determineDatasourceKey(invocation);DynamicDataSourceContextHolder.push(dsKey);try {return invocation.proceed();} finally {DynamicDataSourceContextHolder.poll();}}private String determineDatasourceKey(MethodInvocation invocation) {String key = dataSourceClassResolver.findDSKey(invocation.getMethod(), invocation.getThis());return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key;}
}

在invoke方法执行前,会先把该数据源对应的key加入到线程本地变量的双端队列中,如下。

private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {@Overrideprotected Deque<String> initialValue() {return new ArrayDeque<>();}};

在invoke方法执行后,会先把该数据源对应的key从线程本地变量的双端队列中移除。

看到这里,我们知道,dynamic-datasource-spring-boot-starter会通过DynamicDataSourceAnnotationInterceptor 拦截器获取到@DS注解标识的方法或者类的代理。而@DS注解标注的就是数据源的Key。有了该key, Mybatis就可以获取到该key对应的Druid数据库连接池了。具体的策略,dynamic-datasource-spring-boot-starter提供了轮询和随机两种,这里就不再赘述了。

数据源的切换逻辑在DynamicRoutingDataSource类中。它继承了AbstractRoutingDataSource类,该类实现了getConnection()方法。

public abstract class AbstractRoutingDataSource extends AbstractDataSource {protected abstract DataSource determineDataSource();@Overridepublic Connection getConnection() throws SQLException {String xid = TransactionContext.getXID();if (StringUtils.isEmpty(xid)) {// 核心方法determineDataSourcereturn determineDataSource().getConnection();} else {String ds = DynamicDataSourceContextHolder.peek();ConnectionProxy connection = ConnectionFactory.getConnection(ds);return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;}}@Overridepublic Connection getConnection(String username, String password) throws SQLException {String xid = TransactionContext.getXID();if (StringUtils.isEmpty(xid)) {return determineDataSource().getConnection(username, password);} else {String ds = DynamicDataSourceContextHolder.peek();ConnectionProxy connection = ConnectionFactory.getConnection(ds);return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection(username, password)): connection;}}
}

DynamicDataSourceAnnotationInterceptor的invoke方法负责放数据源的key,
determineDataSource方法负责从线程本地变量的双端队列中取出数据源的key。

public DataSource determineDataSource() {String dsKey = DynamicDataSourceContextHolder.peek();return getDataSource(dsKey);}

getDataSource方法用来获取具体的数据库连接。

    private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();public DataSource getDataSource(String ds) {if (StringUtils.isEmpty(ds)) {return determinePrimaryDataSource();} else if (!groupDataSources.isEmpty() && groupDataSources.containsKey(ds)) {log.debug("dynamic-datasource switch to the datasource named [{}]", ds);return groupDataSources.get(ds).determineDataSource();} else if (dataSourceMap.containsKey(ds)) {log.debug("dynamic-datasource switch to the datasource named [{}]", ds);// map中存了所有的数据库连接信息return dataSourceMap.get(ds);}if (strict) {throw new CannotFindDataSourceException("dynamic-datasource could not find a datasource named" + ds);}return determinePrimaryDataSource();}

那么问题来了,dataSourceMap的数据库相关信息是什么时候存进去的呢?

大家还记得上一篇博客中,我们提到dynamic-datasource-spring-boot-starter的自动配置类DynamicDataSourceAutoConfiguration在初始化的时候还创建了一个DynamicDataSourceProvider类型的bean嘛?没错,dataSourceMap是它从DynamicDataSourceProperties配置类中获取,然后存储进去的key是数据源的唯一标识,如value是数据库的相关信息,包括url, username, password等

	@Bean@ConditionalOnMissingBean // 确保Spring容器中不存在 DynamicDataSourceProvider 类型的 Bean 时才创建该 Beanpublic DynamicDataSourceProvider dynamicDataSourceProvider() {Map<String, DataSourceProperty> datasourceMap = this.properties.getDatasource();return new YmlDynamicDataSourceProvider(datasourceMap);}public class YmlDynamicDataSourceProvider extends AbstractDataSourceProvider {/*** 所有数据源*/private final Map<String, DataSourceProperty> dataSourcePropertiesMap;@Overridepublic Map<String, DataSource> loadDataSources() {// 核心方法return createDataSourceMap(dataSourcePropertiesMap);}
}
protected Map<String, DataSource> createDataSourceMap(Map<String, DataSourceProperty> dataSourcePropertiesMap) {// dataSourcePropertiesMap 就是从DynamicDataSourceProperties配置类中拿到的所有数据库信息Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {DataSourceProperty dataSourceProperty = item.getValue();String poolName = dataSourceProperty.getPoolName();if (poolName == null || "".equals(poolName)) {poolName = item.getKey();}dataSourceProperty.setPoolName(poolName);dataSourceMap.put(poolName, defaultDataSourceCreator.createDataSource(dataSourceProperty));}return dataSourceMap;}

最后,我们来总结下,dynamic-datasource-spring-boot-starter本质还是通过AOP实现了多数据源的切换,实现思路和编码规范、风格等都值得大家深入研究和学习。先研究透一个,其它的SpringBoot的常用starter就会手到擒来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/891175.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI辅助编码提高病案首页主要诊断编码正确率数据优化方法(2025增量优化版附python源代码)

一、引言 1.1 研究背景与意义 在医疗信息化进程中,病案首页作为病历信息的核心浓缩,承载着疾病分类、医疗统计、医保结算等关键任务,其主要诊断编码的准确性至关重要。准确的编码不仅是医疗质量评估、科研数据分析的基石,更是合理分配医疗资源、保障医保精准支付的关键依…

雷电模拟器安装LSPosed

雷电模拟器最新版支持LSPosed。记录一下安装过程 首先到官网下载并安装最新版&#xff0c;我安装的时候最新版是9.1.34.0&#xff0c;64位 然后开启root和系统文件读写 然后下载magisk-delta-6并安装 ,这个是吾爱破解论坛提供的&#xff0c;号称适配安卓7以上所有机型&#x…

模型 10-10-10旁观思维

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。超脱当下&#xff0c;透视决策长远影响。 1 10-10-10旁观思维的应用 1.1 职业选择决策 背景&#xff1a;小张是一名大学毕业生&#xff0c;面对未来职业的选择感到迷茫。他擅长营销、策略和经济学&a…

ORM框架详解:为什么不直接写SQL?

想象一下&#xff0c;你正在开发一个小型的在线书店应用。你需要存储书籍信息、用户数据和订单记录。作为一个初学者&#xff0c;你可能会想&#xff1a;“我已经学会了SQL&#xff0c;为什么还要使用ORM框架呢&#xff1f;直接写SQL语句不是更简单、更直接吗&#xff1f;” 如…

合合信息亮相CSIG AI可信论坛,全面拆解AI视觉内容安全的“终极防线”

合合信息亮相CSIG AI可信论坛&#xff0c;全面拆解视觉内容安全的“终极防线”&#xff01; &#x1f42f; AI伪造泛滥&#xff0c;我们还能相信“眼见为实”吗&#xff1f; 近期&#xff0c;由中国图象图形学学会主办的CSIG青年科学家会议 AI可信论坛在杭州成功举办。本次论…

Linux实验报告9-进程管理

目录 一&#xff1a;实验目的 二&#xff1a;实验内容 (1)列出当前系统中的所有进程,如何观察进程的优先级? (2)查看当前终端运行的 bash 进程的 PID,在当前终端启动 vim 编辑器并让其在后台执行,然后列出在当前终端中执行的进程的家族树。 (3)请自行挂载U盘或光盘,然后…

17爬虫:关于DrissionPage相关内容的学习01

概述 前面我们已经大致了解了selenium的用法&#xff0c;DerssionPage同selenium一样&#xff0c;也是一个基于Python的网页自动化工具。 DrissionPage既可以实现网页的自动化操作&#xff0c;也能够实现收发数据包&#xff0c;也可以把两者的功能合二为一。 DressionPage的…

【Unity3D】Jobs、Burst并行计算裁剪Texture3D物体

版本&#xff1a;Unity2019.4.0f1 PackageManager下载Burst插件(1.2.3版本) 利用如下代码&#xff0c;生成一个Texture3D资源&#xff0c;它只能脚本生成&#xff0c;是一个32*32*32的立方体&#xff0c;导出路径记得改下&#xff0c;不然报错。 using UnityEditor; using Uni…

最短路径-Dijkstra 算法

前言 迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的&#xff0c;因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法&#xff0c;解决的是有向图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展&#xff0c;直到扩展到终点…

ESP32 I2S音频总线学习笔记(一):初识I2S通信与配置基础

文章目录 简介为什么需要I2S&#xff1f;关于音频信号采样率分辨率音频声道 怎样使用I2S传输音频&#xff1f;位时钟BCLK字时钟WS串行数据SD I2S传输模型I2S通信格式I2S格式左对齐格式右对齐格式 i2s基本配置i2s 底层API加载I2S驱动设置I2S使用的引脚I2S读取数据I2S发送数据卸载…

Eclipse中引入NS3项目

参考资料&#xff1a; 博主&#xff1a;深度不睡觉 NS3的3.36版本将Eclipse作IDE_ns3使用eclipse-CSDN博客 从1.2安装eclipse开始 其中参考教程中省略的几点&#xff1a; 1.下载解压tar包 mkdir /Tools/Eclipse/EclipseTool # 新建目录 tar -zxvf /path/to/eclipse-cpp-20…

机器学习周报-TCN文献阅读

文章目录 摘要Abstract 1 TCN通用架构1.1 序列建模任务描述1.2 因果卷积&#xff08;Causal Convolutions&#xff09;1.3 扩张卷积&#xff08;Dilated Convolutions&#xff09;1.4 残差连接&#xff08;Residual Connections&#xff09; 2 TCN vs RNN3 TCN缺点4 代码4.1 TC…

Quartz任务调度框架实现任务动态执行

说明&#xff1a;之前使用Quartz&#xff0c;都是写好Job&#xff0c;指定一个时间点&#xff0c;到点执行。最近有个需求&#xff0c;需要根据前端用户设置的时间点去执行&#xff0c;也就是说任务执行的时间点是动态变化的。本文介绍如何用Quartz任务调度框架实现任务动态执行…

Spring-kafka快速Demo示例

使用Spring-Kafka快速发送/接受Kafka消息示例代码&#xff0c;项目结构是最基础的SpringBoot结构&#xff0c;提前安装好Kafka&#xff0c;确保Kafka已经正确启动 pom.xml&#xff0c;根据个人情况更换springboot、java版本等 <?xml version"1.0" encoding&qu…

【C++】B2079 求出 e 的值

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目介绍输入格式输出格式输入输出样例说明/提示 &#x1f4af;实现方法一&#xff1a;单层 for 循环计算代码实现运行逻辑解析优点不足 &#x1f4af;实现方法二&#xff…

STM32配合可编程加密芯片SMEC88ST的防抄板加密方案设计

SMEC88ST SDK卡发包下载 目前市场上很多嵌入式产品方案都是可以破解复制的&#xff0c;主要是因为方案主芯片不具备防破解的功能&#xff0c;这就导致开发者投入大量精力、财力开发的新产品一上市就被别人复制&#xff0c;到市场上的只能以价格竞争&#xff0c;最后工厂复制的产…

精准识别花生豆:基于EfficientNetB0的深度学习检测与分类项目

精准检测花生豆&#xff1a;基于EfficientNet的深度学习分类项目 在现代农业生产中&#xff0c;作物的质量检测和分类是确保产品质量的重要环节。针对花生豆的检测与分类需求&#xff0c;我们开发了一套基于深度学习的解决方案&#xff0c;利用EfficientNetB0模型实现高效、准…

MarkItDown的使用(将Word、Excel、PDF等转换为Markdown格式)

MarkItDown的使用&#xff08;将Word、Excel、PDF等转换为Markdown格式&#xff09; 本文目录&#xff1a; 零、时光宝盒&#x1f33b; 一、简介 二、安装 三、使用方法 3.1、使用命令行形式 3.2、用 Python 调用 四、总结 五、参考资料 零、时光宝盒&#x1f33b; &a…

Qanything 2.0源码解析系列6 PDF解析逻辑

Qanything 2.0源码解析系列6: PDF解析逻辑 type: Post status: Published date: 2024/12/04 summary: 深入剖析Qanything是如何拆解PDF的,核心是pdf转markdown category: 技术分享 原文:www.feifeixu.top 😀 前言: 在前面的文章中探究了图片是怎么进行解析的,这篇文章对…

【Agent】Chatbot、Copilot与Agent如何帮助我们的提升效率?

人工智能&#xff08;AI&#xff09;技术的迅猛发展正在深刻改变我们的生活和工作方式。你是否曾想过&#xff0c;未来的工作场景会是什么样子&#xff1f;AI的崛起不仅仅是科技的进步&#xff0c;更是我们生活方式的革命。今天&#xff0c;我们将深入探讨三种主要的AI能力&…