Spring动态多数据源源码分析及解读
一、为什么要研究Spring动态多数据源
期初,最开始的原因是:想将答题服务中发送主观题答题数据给批改中间件这块抽象出来, 但这块主要使用的是mq消息的方式发送到批改中间件,所以,最后决定将mq进行抽象,抽象后的结果是:语文,英语,通用任务都能个性化的配置mq,且可以扩展到任何使用mq的业务场景上。终端需要做的就是增加mq配置,自定义消费者业务逻辑方法,调用send方法即可。
这样做的好处是:原本在每个使用到mq的项目里都要写一遍mq生产者,mq消费者,发送mq数据,监听mq消费等动作,且如果一个项目里有多个mq配置,要写多遍这样的配置。抽象后,只需要配置文件中进行配置,然后自定义个性化的业务逻辑消费者,就可以进行mq发送了。
这样一个可动态配置的mq,要求还是挺多的,如何动态配置? 如何能够在服务器启动的时候就启动n个mq的生产者和消费者? 发送数据的时候, 怎么找到正确的mq发送呢?
其实, 我一直相信, 我遇到的问题, 肯定有大神已经遇到过, 并且已经有了成熟的解决方案了. 于是, 开始搜索行业内的解决方案, 找了很久也没找到,最后在同事的提示下,发现Spring动态配置多数据源的思想和我想实现的动态配置多MQ的思想类似。于是,我开始花时间研究Spring动态多数据源的源码。
二、Spring动态多数据源框架梳理
2.1 框架结构
Spring动态多数据源是一个我们在项目中常用到的组件,尤其是做项目重构,有多种数据库,不同的请求可能会调用不同的数据源。这时,就需要动态调用指定的数据源。我们来看看Spring动态多数据源的整体框架
上图中虚线框部分是Spring动态多数据源的几个组成部分
- ds处理器
- aop切面
- 创建数据源
- 动态数据源提供者
- 动态连接数据库
除此之外,还可以看到如下信息:
- Spring动态多数据源是通过动态配置配置文件的方式来指定多数据源的。
- Spring动态多数据源支持四种类型的数据:base数据源,jndi数据源,druid数据源,hikari数据源。
- 多种触发机制:通过header配置ds,通过session配置ds,通过spel配置ds,其中ds是datasource的简称。
- 支持数据源嵌套:一个请求过来,这个请求可能会访问多个数据源,也就是方法嵌套的时候调用多数据源,也是支持的。
2.2 源码结构
Spring动态多数据源的几个组成部分,在代码源码结构中完美的体现出来。
上图是Spring动态多数据源的源码项目结构,我们主要列一下主要的结构
----annotation:定义了DS主机 ----aop:定义了一个前置通知,切面类 ----creator:动态多数据源的创建器 ----exception:异常处理 ----matcher:匹配器 ----processor:ds处理器 ----provider:数据员提供者 ----spring:spring动态多数据源启动配置相关类 ----toolkit:工具包 ----AbstractRoutingDataSource:动态路由数据源抽象类 ----DynamicRoutingDataSource:动态路由数据源实现类
2.3 整体项目结构图
下图是Spring多态多数据源的代码项目结构图。
这个图内容比较多,所以字比较小,大概看出一共有6个部分就可以了。后面会就每一个部分详细说明。
三、项目源码分析
3.1 引入Spring依赖jar包.
Spring动态多数据源,我们在使用的时候,直接引入jar,然后配置数据源就可以使用了。配置jar包如下
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.1.1</version> </dependency>
然后是在yml配置文件中增加配置
# master spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.jdbc.Driver spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.dynamic.datasource.master.username=root spring.datasource.dynamic.datasource.master.password=123456 # slave spring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.jdbc.Driver spring.datasource.dynamic.datasource.slave.url=jdbc:mysql://localhost:3306/test1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.dynamic.datasource.slave.username=root spring.datasource.dynamic.datasource.slave.password=123456
在测试的时候, 使用了两个不同的数据库, 一个是test,一个是test1
3.2 Spring 源码分析的入口
为什么引入jar就能在项目里使用了呢?因为在jar包里配置了META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration
在这个文件里,指定了spring动态加载的时候要自动扫描的文件
DynamicDataSourceAutoConfiguration,这个文件就是源码项目的入口了。这里定义了项目启动自动装备DynamicDataSourceAutoConfiguration文件。
接下来,我们就来看看DynamicDataSourceAutoConfiguration文件。
3.3、Spring配置文件入口。
下图是DynamicDataSourceAutoConfiguration文件的主要内容。
Spring配置文件主要的作用是在系统加载的时候,就加载相关的bean。这里项目初始化的时候都加载了哪些bean呢?
- 动态数据源属性类DynamicDataSourceProperties
- 数据源处理器DsProcessor,采用责任链设计模式3种方法加载ds
- 动态数据源注解类DynamicDataSourceAnnotationAdvisor,包括前置通知,切面类,切点的加载
- 数据源创建器DataSourceCreator,这个方法是在另一个类被加载的DynamicDataSourceCreatorAutoConfiguration。也是自动配置bean类。可以选择4种类型的数据源进行创建。
- 数据源提供者Provider,这是动态初始化数据源,读取yml配置文件,在配置文件中可配置1个或多个数据源。
接下来看一下源代码
1. DynamicDataSourceAutoConfiguration动态数据源配置文件
@Slf4j @Configuration @AllArgsConstructor @EnableConfigurationProperties(DynamicDataSourceProperties.class) @AutoConfigureBefore(DataSourceAutoConfiguration.class) @Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class}) @ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) public class DynamicDataSourceAutoConfiguration { private final DynamicDataSourceProperties properties; @Bean @ConditionalOnMissingBean public DynamicDataSourceProvider dynamicDataSourceProvider() { Map<String, DataSourceProperty> datasourceMap = properties.getDatasource(); return new YmlDynamicDataSourceProvider(datasourceMap); } @Bean @ConditionalOnMissingBean public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) { DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource(); dataSource.setPrimary(properties.getPrimary()); dataSource.setStrict(properties.getStrict()); dataSource.setStrategy(properties.getStrategy()); dataSource.setProvider(dynamicDataSourceProvider); dataSource.setP6spy(properties.getP6spy()); dataSource.setSeata(properties.getSeata()); return dataSource; } @Bean @ConditionalOnMissingBean public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) { DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(); interceptor.setDsProcessor(dsProcessor); DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor); advisor.setOrder(properties.getOrder()); return advisor; } @Bean @ConditionalOnMissingBean public DsProcessor dsProcessor() { DsHeaderProcessor headerProcessor = new DsHeaderProcessor(); DsSessionProcessor sessionProcessor = new DsSessionProcessor(); DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor(); headerProcessor.setNextProcessor(sessionProcessor); sessionProcessor.setNextProcessor(spelExpressionProcessor); return headerProcessor; } @Bean @ConditionalOnBean(DynamicDataSourceConfigure.class) public DynamicDataSourceAdvisor dynamicAdvisor(DynamicDataSourceConfigure dynamicDataSourceConfigure, DsProcessor dsProcessor) { DynamicDataSourceAdvisor advisor = new DynamicDataSourceAdvisor(dynamicDataSourceConfigure.getMatchers()); advisor.setDsProcessor(dsProcessor); advisor.setOrder(Ordered.HIGHEST_PRECEDENCE); return advisor; } }
看到这段代码,我们就比较熟悉了,这就是通过注解的方式,在项目启动的时候,自动注入bean。我们来详细看一下,他都注入了哪些内容。
- 动态多数据源预置处理器dsProcess,ds就是datasource的简称。这里主要采用的是责任链设计模式,获取ds。
- 动态多数据源注解通知dynamicDatasourceAnnotationAdvisor,这是一个aop前置通知,当一个请求发生的时候,会触发前置通知,用来确定到底使用哪一个mq消息队列
- 动态多数据源提供者dynamicDataSourceProvider,我们是动态配置多个数据源,那么就有一个解析配置的过程,解析配置就是在这里完成的,解析出多个数据源,然后分别调用数据源创建者去创建数据源。Spring动态多数据源支持数据源的嵌套。
- 动态路由到数据源DynamicRoutingDataSource,当请求过来的时候,也找到对应的数据源了,要建立数据库连接,数据库连接的操作就是在这里完成的。
我们发现在这里就有四个bean的初始化,并没有bean的create创建过程,bean的创建过程是在另一个配置类(DynamicDataSourceCreatorAutoConfiguration)中完成的。
@Slf4j @Configuration @AllArgsConstructor @EnableConfigurationProperties(DynamicDataSourceProperties.class) public class DynamicDataSourceCreatorAutoConfiguration { private final DynamicDataSourceProperties properties; @Bean @ConditionalOnMissingBean public DataSourceCreator dataSourceCreator() { DataSourceCreator dataSourceCreator = new DataSourceCreator(); dataSourceCreator.setBasicDataSourceCreator(basicDataSourceCreator()); dataSourceCreator.setJndiDataSourceCreator(jndiDataSourceCreator()); dataSourceCreator.setDruidDataSourceCreator(druidDataSourceCreator()); dataSourceCreator.setHikariDataSourceCreator(hikariDataSourceCreator()); dataSourceCreator.setGlobalPublicKey(properties.getPublicKey()); return dataSourceCreator; } @Bean @ConditionalOnMissingBean public BasicDataSourceCreator basicDataSourceCreator() { return new BasicDataSourceCreator(); } @Bean @ConditionalOnMissingBean public JndiDataSourceCreator jndiDataSourceCreator() { return new JndiDataSourceCreator(); } @Bean @ConditionalOnMissingBean public DruidDataSourceCreator druidDataSourceCreator() { return new DruidDataSourceCreator(properties.getDruid()); } @Bean @ConditionalOnMissingBean public HikariDataSourceCreator hikariDataSourceCreator() { return new HikariDataSourceCreator(properties.getHikari()); } }
大概是因为考虑到数据的种类比较多,所以将其单独放到了一个配置里面。从上面的源码可以看出,有四种类型的数据源配置。分别是:basic、jndi、druid、hikari。这四种数据源通过组合设计模式被set到DataSourceCreator中。
接下来,分别来看每一个模块都做了哪些事情。
四、通过责任链设计模式获取数据源名称
Spring动态多数据源, 获取数据源名称的方式有3种,这3中方式采用的是责任链方式连续获取的。首先在header中获取,header中没有,去session中获取, session中也没有, 通过spel获取。
上图是DSProcessor处理器的类图。 一个接口量, 三个具体实现类,主要来看一下接口类实现
1. DsProcessor 抽象类
package com.baomidou.dynamic.datasource.processor; import org.aopalliance.intercept.MethodInvocation; public abstract class DsProcessor { private DsProcessor nextProcessor; public void setNextProcessor(DsProcessor dsProcessor) { this.nextProcessor = dsProcessor; } /** * 抽象匹配条件 匹配才会走当前执行器否则走下一级执行器 * * @param key DS注解里的内容 * @return 是否匹配 */ public abstract boolean matches(String key); /** * 决定数据源 * <pre> * 调用底层doDetermineDatasource, * 如果返回的是null则继续执行下一个,否则直接返回 * </pre> * * @param invocation 方法执行信息 * @param key DS注解里的内容 * @return 数据源名称 */ public String determineDatasource(MethodInvocation invocation, String key) { if (matches(key)) { String datasource = doDetermineDatasource(invocation, key); if (datasource == null && nextProcessor != null) { return nextProcessor.determineDatasource(invocation, key); } return datasource; } if (nextProcessor != null) { return nextProcessor.determineDatasource(invocation, key); } return null; } /** * 抽象最终决定数据源 * * @param invocation 方法执行信息 * @param key DS注解里的内容 * @return 数据源名称 */ public abstract String doDetermineDatasource(MethodInvocation invocation, String key); }
这里定义了DsProcessor nextProcessor属性, 下一个处理器。 判断是否获取到了datasource, 如果获取到了则直接返回, 没有获取到,则调用下一个处理器。这个逻辑就是处理器的主逻辑,在determineDatasource(MethodInvocation invocation, String key)方法中实现。
接下来,每一个子类都会自定义实现doDetermineDatasource获取目标数据源的方法。不同的实现类获取数据源的方式是不同的。
下面看看具体实现类的主逻辑代码
2.DsHeaderProcessor: 从请求的header中获取ds数据源名称。
public class DsHeaderProcessor extends DsProcessor { /** * header prefix */ private static final String HEADER_PREFIX = "#header"; @Override public boolean matches(String key) { return key.startsWith(HEADER_PREFIX); } @Override public String doDetermineDatasource(MethodInvocation invocation, String key) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return request.getHeader(key.substring(8)); } }
3.DsSessionProcessor: 从session中获取数据源d名称
public class DsSessionProcessor extends DsProcessor { /** * session开头 */ private static final String SESSION_PREFIX = "#session"; @Override public boolean matches(String key) { return key.startsWith(SESSION_PREFIX); } @Override public String doDetermineDatasource(MethodInvocation invocation, String key) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return request.getSession().getAttribute(key.substring(9)).toString(); } }
4. DsSpelExpressionProcessor: 通过spel表达式获取ds数据源名称
public class DsSpelExpressionProcessor extends DsProcessor { /** * 参数发现器 */ private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); /** * Express语法解析器 */ private static final ExpressionParser PARSER = new SpelExpressionParser(); /** * 解析上下文的模板 * 对于默认不设置的情况下,从参数中取值的方式 #param1 * 设置指定模板 ParserContext.TEMPLATE_EXPRESSION 后的取值方式: #{#param1} * issues: https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/199 */ private ParserContext parserContext = new ParserContext() { @Override public boolean isTemplate() { return false; } @Override public String getExpressionPrefix() { return null; } @Override public String getExpressionSuffix() { return null; } }; @Override public boolean matches(String key) { return true; } @Override public String doDetermineDatasource(MethodInvocation invocation, String key) { Method method = invocation.getMethod(); Object[] arguments = invocation.getArguments(); EvaluationContext context = new MethodBasedEvaluationContext(null, method, arguments, NAME_DISCOVERER); final Object value = PARSER.parseExpression(key, parserContext).getValue(context); return value == null ? null : value.toString(); } public void setParserContext(ParserContext parserContext) { this.parserContext = parserContext; } }
他们三个的层级关系是在哪里定义的呢?在DynamicDataSourceAutoConfiguration.java配置文件中
5. DynamicDataSourceAutoConfiguration.java配置文件
@Bean @ConditionalOnMissingBean public DsProcessor dsProcessor() { DsHeaderProcessor headerProcessor = new DsHeaderProcessor(); DsSessionProcessor sessionProcessor = new DsSessionProcessor(); DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor(); headerProcessor.setNextProcessor(sessionProcessor); sessionProcessor.setNextProcessor(spelExpressionProcessor); return headerProcessor; }
第一层是headerProcessor,第二层是sessionProcessor, 第三层是spelExpressionProcessor。层级调用,最后获得ds。
以上就是对数据源处理器模块的的分析,那么最终在哪里被调用呢?来看下一个模块。