天天看点

一文读懂Spring动态配置多数据源---源码详细分析

Spring动态多数据源源码分析及解读

​ 期初,最开始的原因是:想将答题服务中发送主观题答题数据给批改中间件这块抽象出来, 但这块主要使用的是mq消息的方式发送到批改中间件,所以,最后决定将mq进行抽象,抽象后的结果是:语文,英语,通用任务都能个性化的配置mq,且可以扩展到任何使用mq的业务场景上。终端需要做的就是增加mq配置,自定义消费者业务逻辑方法,调用send方法即可。

​ 这样做的好处是:原本在每个使用到mq的项目里都要写一遍mq生产者,mq消费者,发送mq数据,监听mq消费等动作,且如果一个项目里有多个mq配置,要写多遍这样的配置。抽象后,只需要配置文件中进行配置,然后自定义个性化的业务逻辑消费者,就可以进行mq发送了。

​ 这样一个可动态配置的mq,要求还是挺多的,如何动态配置? 如何能够在服务器启动的时候就启动n个mq的生产者和消费者? 发送数据的时候, 怎么找到正确的mq发送呢?

​ 其实, 我一直相信, 我遇到的问题, 肯定有大神已经遇到过, 并且已经有了成熟的解决方案了. 于是, 开始搜索行业内的解决方案, 找了很久也没找到,最后在同事的提示下,发现Spring动态配置多数据源的思想和我想实现的动态配置多MQ的思想类似。于是,我开始花时间研究Spring动态多数据源的源码。

Spring动态多数据源是一个我们在项目中常用到的组件,尤其是做项目重构,有多种数据库,不同的请求可能会调用不同的数据源。这时,就需要动态调用指定的数据源。我们来看看Spring动态多数据源的整体框架

一文读懂Spring动态配置多数据源---源码详细分析

上图中虚线框部分是Spring动态多数据源的几个组成部分

ds处理器

aop切面

创建数据源

动态数据源提供者

动态连接数据库

除此之外,还可以看到如下信息:

Spring动态多数据源是通过动态配置配置文件的方式来指定多数据源的。

Spring动态多数据源支持四种类型的数据:base数据源,jndi数据源,druid数据源,hikari数据源。

多种触发机制:通过header配置ds,通过session配置ds,通过spel配置ds,其中ds是datasource的简称。

支持数据源嵌套:一个请求过来,这个请求可能会访问多个数据源,也就是方法嵌套的时候调用多数据源,也是支持的。

Spring动态多数据源的几个组成部分,在代码源码结构中完美的体现出来。

一文读懂Spring动态配置多数据源---源码详细分析

上图是Spring动态多数据源的源码项目结构,我们主要列一下主要的结构

下图是Spring多态多数据源的代码项目结构图。

一文读懂Spring动态配置多数据源---源码详细分析

这个图内容比较多,所以字比较小,大概看出一共有6个部分就可以了。后面会就每一个部分详细说明。

Spring动态多数据源,我们在使用的时候,直接引入jar,然后配置数据源就可以使用了。配置jar包如下

然后是在yml配置文件中增加配置

在测试的时候, 使用了两个不同的数据库, 一个是test,一个是test1

为什么引入jar就能在项目里使用了呢?因为在jar包里配置了META-INF/spring.factories

在这个文件里,指定了spring动态加载的时候要自动扫描的文件DynamicDataSourceAutoConfiguration,这个文件就是源码项目的入口了。这里定义了项目启动自动装备DynamicDataSourceAutoConfiguration文件。

接下来,我们就来看看DynamicDataSourceAutoConfiguration文件。

下图是DynamicDataSourceAutoConfiguration文件的主要内容。

一文读懂Spring动态配置多数据源---源码详细分析

Spring配置文件主要的作用是在系统加载的时候,就加载相关的bean。这里项目初始化的时候都加载了哪些bean呢?

动态数据源属性类DynamicDataSourceProperties

数据源处理器DsProcessor,采用责任链设计模式3种方法加载ds

动态数据源注解类DynamicDataSourceAnnotationAdvisor,包括前置通知,切面类,切点的加载

数据源创建器DataSourceCreator,这个方法是在另一个类被加载的DynamicDataSourceCreatorAutoConfiguration。也是自动配置bean类。可以选择4种类型的数据源进行创建。

数据源提供者Provider,这是动态初始化数据源,读取yml配置文件,在配置文件中可配置1个或多个数据源。

接下来看一下源代码

1. DynamicDataSourceAutoConfiguration动态数据源配置文件

看到这段代码,我们就比较熟悉了,这就是通过注解的方式,在项目启动的时候,自动注入bean。我们来详细看一下,他都注入了哪些内容。

动态多数据源预置处理器dsProcess,ds就是datasource的简称。这里主要采用的是责任链设计模式,获取ds。

动态多数据源注解通知dynamicDatasourceAnnotationAdvisor,这是一个aop前置通知,当一个请求发生的时候,会触发前置通知,用来确定到底使用哪一个mq消息队列

动态多数据源提供者dynamicDataSourceProvider,我们是动态配置多个数据源,那么就有一个解析配置的过程,解析配置就是在这里完成的,解析出多个数据源,然后分别调用数据源创建者去创建数据源。Spring动态多数据源支持数据源的嵌套。

动态路由到数据源DynamicRoutingDataSource,当请求过来的时候,也找到对应的数据源了,要建立数据库连接,数据库连接的操作就是在这里完成的。

我们发现在这里就有四个bean的初始化,并没有bean的create创建过程,bean的创建过程是在另一个配置类(DynamicDataSourceCreatorAutoConfiguration)中完成的。

大概是因为考虑到数据的种类比较多,所以将其单独放到了一个配置里面。从上面的源码可以看出,有四种类型的数据源配置。分别是:basic、jndi、druid、hikari。这四种数据源通过组合设计模式被set到DataSourceCreator中。

接下来,分别来看每一个模块都做了哪些事情。

Spring动态多数据源, 获取数据源名称的方式有3种,这3中方式采用的是责任链方式连续获取的。首先在header中获取,header中没有,去session中获取, session中也没有, 通过spel获取。

上图是DSProcessor处理器的类图。 一个接口量, 三个具体实现类,主要来看一下接口类实现

这里定义了DsProcessor nextProcessor属性, 下一个处理器。 判断是否获取到了datasource, 如果获取到了则直接返回, 没有获取到,则调用下一个处理器。这个逻辑就是处理器的主逻辑,在determineDatasource(MethodInvocation invocation, String key)方法中实现。

接下来,每一个子类都会自定义实现doDetermineDatasource获取目标数据源的方法。不同的实现类获取数据源的方式是不同的。

下面看看具体实现类的主逻辑代码

他们三个的层级关系是在哪里定义的呢?在DynamicDataSourceAutoConfiguration.java配置文件中

第一层是headerProcessor,第二层是sessionProcessor, 第三层是spelExpressionProcessor。层级调用,最后获得ds。

以上就是对数据源处理器模块的的分析,那么最终在哪里被调用呢?来看下一个模块。

这一块对应的源代码结构如下:

一文读懂Spring动态配置多数据源---源码详细分析

这个模块里主要有三部分:

切面类:DynamicDataSourceAdvisor,DynamicDataSourceAnnotationAdvisor

切点类:DynamicAspectJExpressionPointcut,DynamicJdkRegexpMethodPointcut

前置通知类:DynamicDataSourceAnnotationInterceptor

他们之间的关系如下。这里主要是aop方面的知识体系。具体项目结构图如下:

因为在项目中使用最多的情况是通过注解的方式来解析,所以,我们重点看一下两个文件

这里入参中有一个是DsProcessor,也就是ds处理器。在determineDatasource中看看DS的value值是否包含#,如果包含就经过dsProcessor处理后获得key,如果不包含#则直接返回注解的value值。

在切面类的构造函数中设置了前置通知和切点。这个类在项目启动的时候就会被加载。所有带有DS注解的方法都会被扫描,在方法被调用的时候触发前置通知。

这是最底层的操作了,创建数据源。至于到底创建哪种类型的数据源,是由上层配置决定的,在这里,定义了4中类型的数据源。 并通过组合的方式,用到那个数据源,就动态的创建哪个数据源。

下面来看这个模块的源代码结构:

一文读懂Spring动态配置多数据源---源码详细分析

这里面定义了一个数据源组合类和四种类型的数据源。我们来看看他们之间的关系

四个基本的数据源类,最后通过DataSourceCreator类组合创建数据源,这里面使用了简单工厂模式创建类。下面来一个一个看看

这里就有两块,一个是类初始化的时候初始化成员变量, 另一个是创建数据源。当被调用createDataSource的时候执行创建数据源,使用的反射机制创建数据源。

这里通过name查找的方式过去datasource

其实,这里面重点方法也是createDataSource(), 如果看不太明白是怎么创建的,一点关系都没有,就知道通过这种方式创建了数据源就ok了。

这里就不多说了, 就是创建hikari类型的数据源。

其实仔细看,就是整合了前面四种类型的数据源,通过简单工厂模式创建实体类。这里是真正的去调用数据源,开始创建的地方。

通过拆解来看,发现,也并不太难。继续来看下一个模块。

数据源提供者是连接配置文件和数据源创建器的桥梁。数据源提供者先去读取配置文件, 将所有的数据源读取到DynamicDataSourceProperties对象的datasource属性中,datasource是一个Map集合,可以用来存储多种类型的数据源。

下面先来看一下数据源提供者的源码结构:

一文读懂Spring动态配置多数据源---源码详细分析

里面一共有四个文件,AbstractDataSourceProvider是父类,其他类继承自这个类,下面来看一下他们的结构

一文读懂Spring动态配置多数据源---源码详细分析

这里的成员变量是数据源数据源创建者dataSourceCreator. 提供了一个创建数据源的方法:createDataSourceMap(...), 这个方法的入参是属性配置文件datasources, 返回值是创建的数据源对象结合.

这里的主要逻辑思想是: 循环遍历从配置文件读取的多个数据源, 然后根据数据源的类型, 调用DataSourceCreator数据源创建器去创建(初始化)数据源, 然后返回已经初始化好的数据源,将其保存到map集合中.

这是一个抽象类, 里面就提供了一个抽象方法, 加载数据源.

这个源码也是非常简单, 继承了AbstractDataSourceProvider抽象类, 实现了DynamicDataSourceProvider接口. 在loadDataSources()方法中, 创建了多数据源, 并返回多数据源的map集合.

这里指的一提的是他的成员变量dataSourcePropertiesMap. 这个变量是什么时候被赋值的呢? 是在项目启动, 扫描配置文件DynamicDataSourceAutoConfiguration的时候被初始化的.

在DynamicDataSourceAutoConfiguration的脑袋上, 有一个注解@EnableConfigurationProperties(DynamicDataSourceProperties.class), 这个注解的作用是自动扫描配置文件,并自动匹配属性值.

然后,将实例化后的属性对象赋值给properties成员变量. 下面来看看DynamicDataSourceProperties.java属性配置文件.

这个文件的功能:

这个文件定义了扫描yml配置文件的属性前缀:spring.datasource.dynamic,

设置了默认的数据库是master主库, strict表示是否严格模式: 如果是严格模式,那么没有配置数据库,却调用了会抛异常, 如果非严格模式, 没有配数据库, 会采用默认的主数据库.

datasource: 用来存储读取到的数据源, 可能有多个数据源, 所以是map的格式

strategy: 这里定义了负载均衡策略, 采用的是策略设计模式: 可以在配置文件中定义, 如果有多个数据源匹配,如何选择. 可选方案: 1. 负载均衡策略, 2. 随机策略.

其他参数就不多说, 比较简单, 见名思意. 以上就是数据源提供者的主要内容了.

这一块主要功能是在调用的时候, 进行动态选择数据源。其源代码结构如下图

一文读懂Spring动态配置多数据源---源码详细分析

我们知道动态数据源可以嵌套,为什么可以嵌套呢,就是这里决定的, 这里一共有四个文件,

1.AbstractRoutingDataSource: 抽象的路由数据源, 这个类主要作用是在找到目标数据源的情况下,连接数据库.

2.DynamicGroupDataSource:动态分组数据源, 在一个请求链接下的所有数据源就是一组. 也就是一个请求过来, 可以嵌套数据源, 这样数据源就有多个, 这多个就是一组.

DynamicRoutingDataSource: 动态路由数据源, 第一类AbstractRoutingDataSource用来连接数据源,那么到底应该链接哪个数据源呢?在这个类里面查找, 如何找呢, 从DynamicDataSourceContextHolder里面获取当前线程的数据源. 然后链接数据库.

DynamicDataSourceConfigure: 基于多种策略的自动切换数据源.

这四个文件的结构关系如下:

先来看看数据源连接是如何实现的:

一类是具体方法,用来进行数据库连接

另一类是抽象方法, 给出一个抽象方法, 子类实现决定最终数据源.

这里定义了分组的概念.

每一个组有一个组名

组里面有多个数据源, 用list存储,指的注意的是, list是一个LinkedList,有顺序的, 因为在调用数据库查询数据的时候, 不能调混了,所以使用顺序列表集合.

选择数据源的策略, 有多个数据源,按照什么策略选择呢?由策略类型来决定.

方法的含义都比较好理解,向这个组里添加数据源,删除数据源,根据策略寻找目标数据源等.

除此之外, 还有一个非常用来的信息, 那就是这个类实现了InitializingBean接口,这个接口提供了一个afterPropertiesSet()方法, 这个方法在bean被初始化完成之后就会被调用. 这里也是整个项目能够被加载的重点.

既然afterPropertiesSet()方法这么重要, 就来看看他主要做了哪些事情吧.

通过数据源提供器获取所有的数据源,

将上一步获得的所有的数据源添加到 dataSourceMap 和 addGroupDataSource 中. 这里获取数据源的操作就完成

顺着这个思路, 如何添加到 dataSourceMap 和 addGroupDataSource中的呢?

注意第一句话, if (ds.contains(UNDERLINE)) 只有ds中有下划线才会走分组数据源. 如果没有下划线,则就是按照单个数据源来处理的. 向组里面添加数据源就不多说了.

除此之外还有一个非常重要的类:DynamicDataSourceContextHolder

保存了当前线程里面所有的数据源. 使用的是ThreadLocal<Deque>.这个类最主要的含义就是ThreadLocal, 保证每个线程获取的是当前线程的数据源.

以上就是整个数据源源码的全部内容, 内容比较多, 部分功能描述不是特别详细. 如有任何疑问, 可以留言, 一起研究.

一文读懂Spring动态配置多数据源---源码详细分析

继续阅读