充满元气的java爱好者 发表于 2021-8-7 12:19:24

一文读懂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动态多数据源的整体框架
https://p3.toutiaoimg.com/large/pgc-image/3182ae3a82b04ef1a3ad624c4c029a4a
上图中虚线框部分是Spring动态多数据源的几个组成部分

[*]ds处理惩罚器
[*]aop切面
[*]创建数据源
[*]动态数据源提供者
[*]动态毗连数据库
除此之外,还可以看到如下信息:

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

Spring动态多数据源的几个组成部分,在代码源码布局中完善的体现出来。
https://p6.toutiaoimg.com/large/pgc-image/c3f28fe8ef3649bb9251fea6f7376a18
上图是Spring动态多数据源的源码项目布局,我们重要列一下重要的布局
----annotation:定义了DS主机----aop:定义了一个前置通知,切面类----creator:动态多数据源的创建器----exception:非常处理惩罚----matcher:匹配器----processor:ds处理惩罚器----provider:数据员提供者----spring:spring动态多数据源启动设置相关类----toolkit:工具包----AbstractRoutingDataSource:动态路由数据源抽象类----DynamicRoutingDataSource:动态路由数据源实现类2.3 整体项目布局图

下图是Spring多态多数据源的代码项目布局图。
https://p6.toutiaoimg.com/large/pgc-image/31c24f9c11c240fb8ecb53b40dd3581a
这个图内容比较多,所以字比较小,大概看出一共有6个部分就可以了。后面会就每一个部分详细分析。
三、项目源码分析

3.1 引入Spring依赖jar包.

Spring动态多数据源,我们在使用的时候,直接引入jar,然后设置数据源就可以使用了。设置jar包如下
            com.baomidou            dynamic-datasource-spring-boot-starter            3.1.1然后是在yml设置文件中增长设置
# masterspring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.jdbc.Driverspring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8spring.datasource.dynamic.datasource.master.username=rootspring.datasource.dynamic.datasource.master.password=123456# slavespring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.jdbc.Driverspring.datasource.dynamic.datasource.slave.url=jdbc:mysql://localhost:3306/test1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8spring.datasource.dynamic.datasource.slave.username=rootspring.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文件的重要内容。
https://p26.toutiaoimg.com/large/pgc-image/2319c86432aa43fdaa45c671b29af174
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 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获取。
https://p9.toutiaoimg.com/large/pgc-image/ac5545a610a54e6f9020f3536702b062
上图是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);    /**   * 决定数据源   *      *   调用底层doDetermineDatasource,   *   如果返回的是null则继续实行下一个,否则直接返回   *      *   * @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。
以上就是对数据源处理惩罚器模块的的分析,那么最终在哪里被调用呢?来看下一个模块。
五、动态数据源注解通知模块

这一块对应的源代码布局如下:
https://p3.toutiaoimg.com/large/pgc-image/8a5c692519cd43b0a55307d739637d92
这个模块里重要有三部分:

[*]切面类:DynamicDataSourceAdvisor,DynamicDataSourceAnnotationAdvisor
[*]切点类:DynamicAspectJExpressionPointcut,DynamicJdkRegexpMethodPointcut
[*]前置通知类:DynamicDataSourceAnnotationInterceptor
他们之间的关系如下。这里重要是aop方面的知识体系。详细项目布局图如下:
https://p6.toutiaoimg.com/large/pgc-image/696093844b7840d8bc30f3569b2ac0e3
因为在项目中使用最多的环境是通过注解的方式来解析,所以,我们重点看一下两个文件
1.DynamicDataSourceAnnotationInterceptor:自定义的前置通知类

public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {    /**   * The identification of SPEL.   */    private static final String DYNAMIC_PREFIX = "#";    private static final DataSourceClassResolver RESOLVER = new DataSourceClassResolver();    @Setter    private DsProcessor dsProcessor;    @Override    public Object invoke(MethodInvocation invocation) throws Throwable {      try {            DynamicDataSourceContextHolder.push(determineDatasource(invocation));            return invocation.proceed();      } finally {            DynamicDataSourceContextHolder.poll();      }    }    private String determineDatasource(MethodInvocation invocation) throws Throwable {      Method method = invocation.getMethod();      DS ds = method.isAnnotationPresent(DS.class) ? method.getAnnotation(DS.class)                : AnnotationUtils.findAnnotation(RESOLVER.targetClass(invocation), DS.class);      String key = ds.value();      return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key;    }}这里入参中有一个是DsProcessor,也就是ds处理惩罚器。在determineDatasource中看看DS的value值是否包含#,如果包含就经过dsProcessor处理惩罚后获得key,如果不包含#则直接返回注解的value值。
2.DynamicDataSourceAnnotationAdvisor 切面类

public class DynamicDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor implements      BeanFactoryAware {    private Advice advice;    private Pointcut pointcut;    public DynamicDataSourceAnnotationAdvisor(@NonNull DynamicDataSourceAnnotationInterceptor dynamicDataSourceAnnotationInterceptor) {      this.advice = dynamicDataSourceAnnotationInterceptor;      this.pointcut = buildPointcut();    }    @Override    public Pointcut getPointcut() {      return this.pointcut;    }    @Override    public Advice getAdvice() {      return this.advice;    }    @Override    public 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 = AnnotationMatchingPointcut.forMethodAnnotation(DS.class);      return new ComposablePointcut(cpc).union(mpc);    }}在切面类的构造函数中设置了前置通知和切点。这个类在项目启动的时候就会被加载。所有带有DS注解的方法都会被扫描,在方法被调用的时候触发前置通知。
六、数据源创建器

这是最底层的操作了,创建数据源。至于到底创建哪种类型的数据源,是由上层设置决定的,在这里,定义了4中类型的数据源。 并通过组合的方式,用到谁人数据源,就动态的创建哪个数据源。
下面来看这个模块的源代码布局:
https://p26.toutiaoimg.com/large/pgc-image/252f526d0ca04eb9bfbce29cb45cbcde
这内里定义了一个数据源组合类和四种类型的数据源。我们来看看他们之间的关系
https://p9.toutiaoimg.com/large/pgc-image/9f8095b9b0574dd198ef211807c4e1ab
四个基本的数据源类,末了通过DataSourceCreator类组合创建数据源,这内里使用了简朴工厂模式创建类。下面来一个一个看看
1.BasicDataSourceCreator:基础数据源创建器

package com.baomidou.dynamic.datasource.creator;import com.baomidou.dynamic.datasource.exception.ErrorCreateDataSourceException;import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;import lombok.Data;import lombok.extern.slf4j.Slf4j;import javax.sql.DataSource;import java.lang.reflect.Method;/** * 基础数据源创建器 * * @author TaoYu * @since 2020/1/21 */@Data@Slf4jpublic class BasicDataSourceCreator {    private static Method createMethod;    private static Method typeMethod;    private static Method urlMethod;    private static Method usernameMethod;    private static Method passwordMethod;    private static Method driverClassNameMethod;    private static Method buildMethod;    static {      //to support springboot 1.5 and 2.x      Class builderClass = null;      try {            builderClass = Class.forName("org.springframework.boot.jdbc.DataSourceBuilder");      } catch (Exception ignored) {      }      if (builderClass == null) {            try {                builderClass = Class.forName("org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder");            } catch (Exception e) {                log.warn("not in springBoot ENV,could not create BasicDataSourceCreator");            }      }      if (builderClass != null) {            try {                createMethod = builderClass.getDeclaredMethod("create");                typeMethod = builderClass.getDeclaredMethod("type", Class.class);                urlMethod = builderClass.getDeclaredMethod("url", String.class);                usernameMethod = builderClass.getDeclaredMethod("username", String.class);                passwordMethod = builderClass.getDeclaredMethod("password", String.class);                driverClassNameMethod = builderClass.getDeclaredMethod("driverClassName", String.class);                buildMethod = builderClass.getDeclaredMethod("build");            } catch (Exception e) {                e.printStackTrace();            }      }    }    /**   * 创建基础数据源   *   * @param dataSourceProperty 数据源参数   * @return 数据源   */    public DataSource createDataSource(DataSourceProperty dataSourceProperty) {      try {            Object o1 = createMethod.invoke(null);            Object o2 = typeMethod.invoke(o1, dataSourceProperty.getType());            Object o3 = urlMethod.invoke(o2, dataSourceProperty.getUrl());            Object o4 = usernameMethod.invoke(o3, dataSourceProperty.getUsername());            Object o5 = passwordMethod.invoke(o4, dataSourceProperty.getPassword());            Object o6 = driverClassNameMethod.invoke(o5, dataSourceProperty.getDriverClassName());            return (DataSource) buildMethod.invoke(o6);      } catch (Exception e) {            throw new ErrorCreateDataSourceException(                  "dynamic-datasource create basic database named " + dataSourceProperty.getPoolName() + " error");      }    }}这里就有两块,一个是类初始化的时候初始化成员变量, 另一个是创建数据源。当被调用createDataSource的时候实行创建数据源,使用的反射机制创建数据源。
2.JndiDataSourceCreator 使用jndi的方式创建数据源

public class JndiDataSourceCreator {    private static final JndiDataSourceLookup LOOKUP = new JndiDataSourceLookup();    /**   * 创建基础数据源   *   * @param name 数据源参数   * @return 数据源   */    public DataSource createDataSource(String name) {      return LOOKUP.getDataSource(name);    }}这里通过name查找的方式过去datasource
3.DruidDataSourceCreator: 创建druid类型的数据源

public class DruidDataSourceCreator {    private DruidConfig druidConfig;    @Autowired(required = false)    private ApplicationContext applicationContext;    public DruidDataSourceCreator(DruidConfig druidConfig) {      this.druidConfig = druidConfig;    }    public DataSource createDataSource(DataSourceProperty dataSourceProperty) {      DruidDataSource dataSource = new DruidDataSource();      dataSource.setUsername(dataSourceProperty.getUsername());      dataSource.setPassword(dataSourceProperty.getPassword());      dataSource.setUrl(dataSourceProperty.getUrl());      dataSource.setDriverClassName(dataSourceProperty.getDriverClassName());      dataSource.setName(dataSourceProperty.getPoolName());      DruidConfig config = dataSourceProperty.getDruid();      Properties properties = config.toProperties(druidConfig);      String filters = properties.getProperty("druid.filters");      List proxyFilters = new ArrayList(2);      if (!StringUtils.isEmpty(filters) && filters.contains("stat")) {            StatFilter statFilter = new StatFilter();            statFilter.configFromProperties(properties);            proxyFilters.add(statFilter);      }      if (!StringUtils.isEmpty(filters) && filters.contains("wall")) {            WallConfig wallConfig = DruidWallConfigUtil.toWallConfig(dataSourceProperty.getDruid().getWall(), druidConfig.getWall());            WallFilter wallFilter = new WallFilter();            wallFilter.setConfig(wallConfig);            proxyFilters.add(wallFilter);      }      if (!StringUtils.isEmpty(filters) && filters.contains("slf4j")) {            Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();            // 由于properties上面被用了,LogFilter不能使用configFromProperties方法,这里只能一个个set了。            DruidSlf4jConfig slf4jConfig = druidConfig.getSlf4j();            slf4jLogFilter.setStatementLogEnabled(slf4jConfig.getEnable());            slf4jLogFilter.setStatementExecutableSqlLogEnable(slf4jConfig.getStatementExecutableSqlLogEnable());            proxyFilters.add(slf4jLogFilter);      }      if (this.applicationContext != null) {            for (String filterId : druidConfig.getProxyFilters()) {                proxyFilters.add(this.applicationContext.getBean(filterId, Filter.class));            }      }      dataSource.setProxyFilters(proxyFilters);      dataSource.configFromPropety(properties);      //毗连参数单独设置      dataSource.setConnectProperties(config.getConnectionProperties());      //设置druid内置properties不支持的的参数      Boolean testOnReturn = config.getTestOnReturn() == null ? druidConfig.getTestOnReturn() : config.getTestOnReturn();      if (testOnReturn != null && testOnReturn.equals(true)) {            dataSource.setTestOnReturn(true);      }      Integer validationQueryTimeout =                config.getValidationQueryTimeout() == null ? druidConfig.getValidationQueryTimeout() : config.getValidationQueryTimeout();      if (validationQueryTimeout != null && !validationQueryTimeout.equals(-1)) {            dataSource.setValidationQueryTimeout(validationQueryTimeout);      }      Boolean sharePreparedStatements =                config.getSharePreparedStatements() == null ? druidConfig.getSharePreparedStatements() : config.getSharePreparedStatements();      if (sharePreparedStatements != null && sharePreparedStatements.equals(true)) {            dataSource.setSharePreparedStatements(true);      }      Integer connectionErrorRetryAttempts =                config.getConnectionErrorRetryAttempts() == null ? druidConfig.getConnectionErrorRetryAttempts()                        : config.getConnectionErrorRetryAttempts();      if (connectionErrorRetryAttempts != null && !connectionErrorRetryAttempts.equals(1)) {            dataSource.setConnectionErrorRetryAttempts(connectionErrorRetryAttempts);      }      Boolean breakAfterAcquireFailure =                config.getBreakAfterAcquireFailure() == null ? druidConfig.getBreakAfterAcquireFailure() : config.getBreakAfterAcquireFailure();      if (breakAfterAcquireFailure != null && breakAfterAcquireFailure.equals(true)) {            dataSource.setBreakAfterAcquireFailure(true);      }      Integer timeout = config.getRemoveAbandonedTimeoutMillis() == null ? druidConfig.getRemoveAbandonedTimeoutMillis()                : config.getRemoveAbandonedTimeoutMillis();      if (timeout != null) {            dataSource.setRemoveAbandonedTimeout(timeout);      }      Boolean abandoned = config.getRemoveAbandoned() == null ? druidConfig.getRemoveAbandoned() : config.getRemoveAbandoned();      if (abandoned != null) {            dataSource.setRemoveAbandoned(abandoned);      }      Boolean logAbandoned = config.getLogAbandoned() == null ? druidConfig.getLogAbandoned() : config.getLogAbandoned();      if (logAbandoned != null) {            dataSource.setLogAbandoned(logAbandoned);      }      Integer queryTimeOut = config.getQueryTimeout() == null ? druidConfig.getQueryTimeout() : config.getQueryTimeout();      if (queryTimeOut != null) {            dataSource.setQueryTimeout(queryTimeOut);      }      Integer transactionQueryTimeout =                config.getTransactionQueryTimeout() == null ? druidConfig.getTransactionQueryTimeout() : config.getTransactionQueryTimeout();      if (transactionQueryTimeout != null) {            dataSource.setTransactionQueryTimeout(transactionQueryTimeout);      }      try {            dataSource.init();      } catch (SQLException e) {            throw new ErrorCreateDataSourceException("druid create error", e);      }      return dataSource;    }}其实,这内里重点方法也是createDataSource(), 如果看不太明白是怎么创建的,一点关系都没有,就知道通过这种方式创建了数据源就ok了。
4. HikariDataSourceCreator: 创建Hikari类型的数据源

@Data@AllArgsConstructorpublic class HikariDataSourceCreator {    private HikariCpConfig hikariCpConfig;    public DataSource createDataSource(DataSourceProperty dataSourceProperty) {      HikariConfig config = dataSourceProperty.getHikari().toHikariConfig(hikariCpConfig);      config.setUsername(dataSourceProperty.getUsername());      config.setPassword(dataSourceProperty.getPassword());      config.setJdbcUrl(dataSourceProperty.getUrl());      config.setDriverClassName(dataSourceProperty.getDriverClassName());      config.setPoolName(dataSourceProperty.getPoolName());      return new HikariDataSource(config);    }}这里就不多说了, 就是创建hikari类型的数据源。
5.DataSourceCreator数据源创建器

@Slf4j@Setterpublic class DataSourceCreator {    /**   * 是否存在druid   */    private static Boolean druidExists = false;    /**   * 是否存在hikari   */    private static Boolean hikariExists = false;    static {      try {            Class.forName(DRUID_DATASOURCE);            druidExists = true;            log.debug("dynamic-datasource detect druid,Please Notice \n " +                  "https://github.com/baomidou/dynamic-datasource-spring-boot-starter/wiki/Integration-With-Druid");      } catch (ClassNotFoundException ignored) {      }      try {            Class.forName(HIKARI_DATASOURCE);            hikariExists = true;      } catch (ClassNotFoundException ignored) {      }    }    private BasicDataSourceCreator basicDataSourceCreator;    private JndiDataSourceCreator jndiDataSourceCreator;    private HikariDataSourceCreator hikariDataSourceCreator;    private DruidDataSourceCreator druidDataSourceCreator;    private String globalPublicKey;    /**   * 创建数据源   *   * @param dataSourceProperty 数据源信息   * @return 数据源   */    public DataSource createDataSource(DataSourceProperty dataSourceProperty) {      DataSource dataSource;      //如果是jndi数据源      String jndiName = dataSourceProperty.getJndiName();      if (jndiName != null && !jndiName.isEmpty()) {            dataSource = createJNDIDataSource(jndiName);      } else {            Class

Deathef 发表于 2021-8-7 15:10:03

这个开源库并不好用。第一不支持运行时增加读库,这一点就会被运维部直接pass了。第二不支持多租户,这个就会被做SaaS业务的直接pass了。

Marianas 发表于 2021-8-7 14:26:08

转发了

且行且珍惜42960936 发表于 2021-8-7 13:41:33

转发了
页: [1]
查看完整版本: 一文读懂Spring动态设置多数据源——源码详细分析