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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 一文读懂Spring动态配置多数据源---源码详细分析

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动态多数据源的整体框架

4ca4073da3944db2b4e18bd52ce6561f_tplv-k3u1fbpfcp-zoom-1.png


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


  1. ds处理器
  2. aop切面
  3. 创建数据源
  4. 动态数据源提供者
  5. 动态连接数据库


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


  1. Spring动态多数据源是通过动态配置配置文件的方式来指定多数据源的。
  2. Spring动态多数据源支持四种类型的数据:base数据源,jndi数据源,druid数据源,hikari数据源。
  3. 多种触发机制:通过header配置ds,通过session配置ds,通过spel配置ds,其中ds是datasource的简称。
  4. 支持数据源嵌套:一个请求过来,这个请求可能会访问多个数据源,也就是方法嵌套的时候调用多数据源,也是支持的。


2.2 源码结构


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

554e588c260d415f8fff987faf5cd3ff_tplv-k3u1fbpfcp-zoom-1.png


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


----annotation:定义了DS主机
----aop:定义了一个前置通知,切面类
----creator:动态多数据源的创建器
----exception:异常处理
----matcher:匹配器
----processor:ds处理器
----provider:数据员提供者
----spring:spring动态多数据源启动配置相关类
----toolkit:工具包
----AbstractRoutingDataSource:动态路由数据源抽象类
----DynamicRoutingDataSource:动态路由数据源实现类

2.3 整体项目结构图


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

154291b53e37420b86b3c98b0f5e6307_tplv-k3u1fbpfcp-zoom-1.jpg


这个图内容比较多,所以字比较小,大概看出一共有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文件的主要内容。

224569b5835c42338178b1345b26cbbd_tplv-k3u1fbpfcp-zoom-1.png

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


  1. 动态数据源属性类DynamicDataSourceProperties
  2. 数据源处理器DsProcessor,采用责任链设计模式3种方法加载ds
  3. 动态数据源注解类DynamicDataSourceAnnotationAdvisor,包括前置通知,切面类,切点的加载
  4. 数据源创建器DataSourceCreator,这个方法是在另一个类被加载的DynamicDataSourceCreatorAutoConfiguration。也是自动配置bean类。可以选择4种类型的数据源进行创建。
  5. 数据源提供者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。我们来详细看一下,他都注入了哪些内容。


  1. 动态多数据源预置处理器dsProcess,ds就是datasource的简称。这里主要采用的是责任链设计模式,获取ds。
  2. 动态多数据源注解通知dynamicDatasourceAnnotationAdvisor,这是一个aop前置通知,当一个请求发生的时候,会触发前置通知,用来确定到底使用哪一个mq消息队列
  3. 动态多数据源提供者dynamicDataSourceProvider,我们是动态配置多个数据源,那么就有一个解析配置的过程,解析配置就是在这里完成的,解析出多个数据源,然后分别调用数据源创建者去创建数据源。Spring动态多数据源支持数据源的嵌套。
  4. 动态路由到数据源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获取。


46b0cec475c74e1fa66871620dbc9aa1_tplv-k3u1fbpfcp-zoom-1.png

上图是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。

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

相关文章
|
1月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
1月前
|
Java 关系型数据库 MySQL
Spring Boot自动配置:魔法背后的秘密
Spring Boot 自动配置揭秘:只需简单配置即可启动项目,背后依赖“约定大于配置”与条件化装配。核心在于 `@EnableAutoConfiguration` 注解与 `@Conditional` 系列条件判断,通过 `spring.factories` 或 `AutoConfiguration.imports` 加载配置类,实现按需自动装配 Bean。
|
1月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
3月前
|
Java Spring
Spring Boot配置的优先级?
在Spring Boot项目中,配置可通过配置文件和外部配置实现。支持的配置文件包括application.properties、application.yml和application.yaml,优先级依次降低。外部配置常用方式有Java系统属性(如-Dserver.port=9001)和命令行参数(如--server.port=10010),其中命令行参数优先级高于系统属性。整体优先级顺序为:命令行参数 &gt; Java系统属性 &gt; application.properties &gt; application.yml &gt; application.yaml。
678 0
|
1月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
272 4
|
1月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
102 0
探索Spring Boot的@Conditional注解的上下文配置
|
2月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
778 10
|
2月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
Java 应用服务中间件 数据库连接
Spring全家桶之Spring篇深度分析(一)
Spring 框架不局限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
Spring全家桶之Spring篇深度分析(一)
|
3月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
775 0