zoukankan      html  css  js  c++  java
  • 一文读懂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动态多数据源的整体框架

    上图中虚线框部分是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动态多数据源的几个组成部分,在代码源码结构中完美的体现出来。

    上图是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呢?

    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获取。

    上图是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. 切面类:DynamicDataSourceAdvisor,DynamicDataSourceAnnotationAdvisor
    2. 切点类:DynamicAspectJExpressionPointcut,DynamicJdkRegexpMethodPointcut
    3. 前置通知类:DynamicDataSourceAnnotationInterceptor

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

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

    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中类型的数据源。 并通过组合的方式,用到那个数据源,就动态的创建哪个数据源。

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

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

    四个基本的数据源类,最后通过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
    @Slf4j
    public 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<Filter> 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
    @AllArgsConstructor
    public 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
    @Setter
    public 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 
     " +
                        "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<? extends DataSource> type = dataSourceProperty.getType();
                if (type == null) {
                    if (druidExists) {
                        dataSource = createDruidDataSource(dataSourceProperty);
                    } else if (hikariExists) {
                        dataSource = createHikariDataSource(dataSourceProperty);
                    } else {
                        dataSource = createBasicDataSource(dataSourceProperty);
                    }
                } else if (DRUID_DATASOURCE.equals(type.getName())) {
                    dataSource = createDruidDataSource(dataSourceProperty);
                } else if (HIKARI_DATASOURCE.equals(type.getName())) {
                    dataSource = createHikariDataSource(dataSourceProperty);
                } else {
                    dataSource = createBasicDataSource(dataSourceProperty);
                }
            }
            this.runScrip(dataSourceProperty, dataSource);
            return dataSource;
        }
    
        private void runScrip(DataSourceProperty dataSourceProperty, DataSource dataSource) {
            String schema = dataSourceProperty.getSchema();
            String data = dataSourceProperty.getData();
            if (StringUtils.hasText(schema) || StringUtils.hasText(data)) {
                ScriptRunner scriptRunner = new ScriptRunner(dataSourceProperty.isContinueOnError(), dataSourceProperty.getSeparator());
                if (StringUtils.hasText(schema)) {
                    scriptRunner.runScript(dataSource, schema);
                }
                if (StringUtils.hasText(data)) {
                    scriptRunner.runScript(dataSource, data);
                }
            }
        }
    
        /**
         * 创建基础数据源
         *
         * @param dataSourceProperty 数据源参数
         * @return 数据源
         */
        public DataSource createBasicDataSource(DataSourceProperty dataSourceProperty) {
            if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
                dataSourceProperty.setPublicKey(globalPublicKey);
            }
            return basicDataSourceCreator.createDataSource(dataSourceProperty);
        }
    
        /**
         * 创建JNDI数据源
         *
         * @param jndiName jndi数据源名称
         * @return 数据源
         */
        public DataSource createJNDIDataSource(String jndiName) {
            return jndiDataSourceCreator.createDataSource(jndiName);
        }
    
        /**
         * 创建Druid数据源
         *
         * @param dataSourceProperty 数据源参数
         * @return 数据源
         */
        public DataSource createDruidDataSource(DataSourceProperty dataSourceProperty) {
            if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
                dataSourceProperty.setPublicKey(globalPublicKey);
            }
            return druidDataSourceCreator.createDataSource(dataSourceProperty);
        }
    
        /**
         * 创建Hikari数据源
         *
         * @param dataSourceProperty 数据源参数
         * @return 数据源
         * @author 离世庭院 小锅盖
         */
        public DataSource createHikariDataSource(DataSourceProperty dataSourceProperty) {
            if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
                dataSourceProperty.setPublicKey(globalPublicKey);
            }
            return hikariDataSourceCreator.createDataSource(dataSourceProperty);
        }
    }
    

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

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

    七、数据源提供者

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

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

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

    1.AbstractDataSourceProvider是整个动态数据源提供者的的抽象类

    public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider {
    
        @Autowired
        private DataSourceCreator dataSourceCreator;
    
        protected Map<String, DataSource> createDataSourceMap(
                Map<String, DataSourceProperty> dataSourcePropertiesMap) {
            Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);
            for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {
                DataSourceProperty dataSourceProperty = item.getValue();
                String pollName = dataSourceProperty.getPoolName();
                if (pollName == null || "".equals(pollName)) {
                    pollName = item.getKey();
                }
                dataSourceProperty.setPoolName(pollName);
                dataSourceMap.put(pollName, dataSourceCreator.createDataSource(dataSourceProperty));
            }
            return dataSourceMap;
        }
    }
    

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

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

    2.DynamicDataSourceProvider动态数据源提供者

    /**
     * 多数据源加载接口,默认的实现为从yml信息中加载所有数据源 你可以自己实现从其他地方加载所有数据源
     *
     */
    public interface DynamicDataSourceProvider {
    
        /**
         * 加载所有数据源
         *
         * @return 所有数据源,key为数据源名称
         */
        Map<String, DataSource> loadDataSources();
    }
    
    

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

    3.YmlDynamicDataSourceProvider使用yml配置文件读取的方式的动态数据源提供者

    @Slf4j
    @AllArgsConstructor
    public class YmlDynamicDataSourceProvider extends AbstractDataSourceProvider implements DynamicDataSourceProvider {
    
        /**
         * 所有数据源
         */
        private Map<String, DataSourceProperty> dataSourcePropertiesMap;
    
        @Override
        public Map<String, DataSource> loadDataSources() {
            return createDataSourceMap(dataSourcePropertiesMap);
        }
    }
    

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

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

    4.DynamicDataSourceAutoConfiguration

    /**
     * 动态数据源核心自动配置类
     *
     * @author TaoYu Kanyuxia
     * @see DynamicDataSourceProvider
     * @see DynamicDataSourceStrategy
     * @see DynamicRoutingDataSource
     * @since 1.0.0
     */
    @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);
        }
    
    }
    

    在DynamicDataSourceAutoConfiguration的脑袋上, 有一个注解@EnableConfigurationProperties(DynamicDataSourceProperties.class), 这个注解的作用是自动扫描配置文件,并自动匹配属性值.
    然后,将实例化后的属性对象赋值给properties成员变量. 下面来看看DynamicDataSourceProperties.java属性配置文件.

    @Slf4j
    @Getter
    @Setter
    @ConfigurationProperties(prefix = DynamicDataSourceProperties.PREFIX)
    public class DynamicDataSourceProperties {
    
        public static final String PREFIX = "spring.datasource.dynamic";
        public static final String HEALTH = PREFIX + ".health";
    
        /**
         * 必须设置默认的库,默认master
         */
        private String primary = "master";
        /**
         * 是否启用严格模式,默认不启动. 严格模式下未匹配到数据源直接报错, 非严格模式下则使用默认数据源primary所设置的数据源
         */
        private Boolean strict = false;
        /**
         * 是否使用p6spy输出,默认不输出
         */
        private Boolean p6spy = false;
        /**
         * 是否使用seata,默认不使用
         */
        private Boolean seata = false;
        /**
         * 是否使用 spring actuator 监控检查,默认不检查
         */
        private boolean health = false;
        /**
         * 每一个数据源
         */
        private Map<String, DataSourceProperty> datasource = new LinkedHashMap<>();
        /**
         * 多数据源选择算法clazz,默认负载均衡算法
         */
        private Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;
        /**
         * aop切面顺序,默认优先级最高
         */
        private Integer order = Ordered.HIGHEST_PRECEDENCE;
        /**
         * Druid全局参数配置
         */
        @NestedConfigurationProperty
        private DruidConfig druid = new DruidConfig();
        /**
         * HikariCp全局参数配置
         */
        @NestedConfigurationProperty
        private HikariCpConfig hikari = new HikariCpConfig();
    
        /**
         * 全局默认publicKey
         */
        private String publicKey = CryptoUtils.DEFAULT_PUBLIC_KEY_STRING;
    }
    

    这个文件的功能:

    1. 这个文件定义了扫描yml配置文件的属性前缀:spring.datasource.dynamic,
    2. 设置了默认的数据库是master主库, strict表示是否严格模式: 如果是严格模式,那么没有配置数据库,却调用了会抛异常, 如果非严格模式, 没有配数据库, 会采用默认的主数据库.
    3. datasource: 用来存储读取到的数据源, 可能有多个数据源, 所以是map的格式
    4. strategy: 这里定义了负载均衡策略, 采用的是策略设计模式: 可以在配置文件中定义, 如果有多个数据源匹配,如何选择. 可选方案: 1. 负载均衡策略, 2. 随机策略.

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

    八、动态路由数据源

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

    我们知道动态数据源可以嵌套,为什么可以嵌套呢,就是这里决定的, 这里一共有四个文件,
    1.AbstractRoutingDataSource: 抽象的路由数据源, 这个类主要作用是在找到目标数据源的情况下,连接数据库.
    2.DynamicGroupDataSource:动态分组数据源, 在一个请求链接下的所有数据源就是一组. 也就是一个请求过来, 可以嵌套数据源, 这样数据源就有多个, 这多个就是一组.

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

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

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

    1.AbstractRoutingDataSource: 这是一个抽象类, 里面主要有两类方法

    一类是具体方法,用来进行数据库连接
    另一类是抽象方法, 给出一个抽象方法, 子类实现决定最终数据源.

    public abstract class AbstractRoutingDataSource extends AbstractDataSource {
    
        /**
         * 子类实现决定最终数据源
         *
         * @return 数据源
         */
        protected abstract DataSource determineDataSource();
    
        @Override
        public Connection getConnection() throws SQLException {
            return determineDataSource().getConnection();
        }
    
        @Override
        public Connection getConnection(String username, String password) throws SQLException {
            return determineDataSource().getConnection(username, password);
        }
    }
    

    2.DynamicGroupDataSource: 动态分组数据源,

    这里定义了分组的概念.

    1. 每一个组有一个组名
    2. 组里面有多个数据源, 用list存储,指的注意的是, list是一个LinkedList,有顺序的, 因为在调用数据库查询数据的时候, 不能调混了,所以使用顺序列表集合.
    3. 选择数据源的策略, 有多个数据源,按照什么策略选择呢?由策略类型来决定.
    public class DynamicGroupDataSource {
    
        private String groupName;
    
        private DynamicDataSourceStrategy dynamicDataSourceStrategy;
    
        private List<DataSource> dataSources = new LinkedList<>();
    
        public DynamicGroupDataSource(String groupName, DynamicDataSourceStrategy dynamicDataSourceStrategy) {
            this.groupName = groupName;
            this.dynamicDataSourceStrategy = dynamicDataSourceStrategy;
        }
    
        public void addDatasource(DataSource dataSource) {
            dataSources.add(dataSource);
        }
    
        public void removeDatasource(DataSource dataSource) {
            dataSources.remove(dataSource);
        }
    
        public DataSource determineDataSource() {
            return dynamicDataSourceStrategy.determineDataSource(dataSources);
        }
    
        public int size() {
            return dataSources.size();
        }
    }
    

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

    3.DynamicRoutingDataSource: 这是外部调用的实现类, 这个类继承自AbstractRoutingDataSource, 所以可以直接调用链接数据库的方法, 并且要重写获取目标数据源的方法. 同时采用组合的方式调用了DynamicGroupDataSource动态分组数据源.

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

    @Slf4j
    public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean {
    
        private static final String UNDERLINE = "_";
        /**
         * 所有数据库
         */
        private final Map<String, DataSource> dataSourceMap = new LinkedHashMap<>();
        /**
         * 分组数据库
         */
        private final Map<String, DynamicGroupDataSource> groupDataSources = new ConcurrentHashMap<>();
        @Setter
        private DynamicDataSourceProvider provider;
        @Setter
        private String primary;
        @Setter
        private boolean strict;
        @Setter
        private Class<? extends DynamicDataSourceStrategy> strategy;
        private boolean p6spy;
        private boolean seata;
    
        @Override
        public DataSource determineDataSource() {
            return getDataSource(DynamicDataSourceContextHolder.peek());
        }
    
        private DataSource determinePrimaryDataSource() {
            log.debug("dynamic-datasource switch to the primary datasource");
            return groupDataSources.containsKey(primary) ? groupDataSources.get(primary).determineDataSource() : dataSourceMap.get(primary);
        }
    
        /**
         * 获取当前所有的数据源
         *
         * @return 当前所有数据源
         */
        public Map<String, DataSource> getCurrentDataSources() {
            return dataSourceMap;
        }
    
        /**
         * 获取的当前所有的分组数据源
         *
         * @return 当前所有的分组数据源
         */
        public Map<String, DynamicGroupDataSource> getCurrentGroupDataSources() {
            return groupDataSources;
        }
    
        /**
         * 获取数据源
         *
         * @param ds 数据源名称
         * @return 数据源
         */
        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);
                return dataSourceMap.get(ds);
            }
            if (strict) {
                throw new RuntimeException("dynamic-datasource could not find a datasource named" + ds);
            }
            return determinePrimaryDataSource();
        }
    
        /**
         * 添加数据源
         *
         * @param ds         数据源名称
         * @param dataSource 数据源
         */
        public synchronized void addDataSource(String ds, DataSource dataSource) {
            if (!dataSourceMap.containsKey(ds)) {
                dataSource = wrapDataSource(ds, dataSource);
                dataSourceMap.put(ds, dataSource);
                this.addGroupDataSource(ds, dataSource);
                log.info("dynamic-datasource - load a datasource named [{}] success", ds);
            } else {
                log.warn("dynamic-datasource - load a datasource named [{}] failed, because it already exist", ds);
            }
        }
    
    
        private void addGroupDataSource(String ds, DataSource dataSource) {
            if (ds.contains(UNDERLINE)) {
                String group = ds.split(UNDERLINE)[0];
                if (groupDataSources.containsKey(group)) {
                    groupDataSources.get(group).addDatasource(dataSource);
                } else {
                    try {
                        DynamicGroupDataSource groupDatasource = new DynamicGroupDataSource(group, strategy.newInstance());
                        groupDatasource.addDatasource(dataSource);
                        groupDataSources.put(group, groupDatasource);
                    } catch (Exception e) {
                        log.error("dynamic-datasource - add the datasource named [{}] error", ds, e);
                        dataSourceMap.remove(ds);
                    }
                }
            }
        }
    
        
    
        @Override
        public void afterPropertiesSet() throws Exception {
            Map<String, DataSource> dataSources = provider.loadDataSources();
            // 添加并分组数据源
            for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {
                addDataSource(dsItem.getKey(), dsItem.getValue());
            }
            // 检测默认数据源设置
            if (groupDataSources.containsKey(primary)) {
                log.info("dynamic-datasource initial loaded [{}] datasource,primary group datasource named [{}]", dataSources.size(), primary);
            } else if (dataSourceMap.containsKey(primary)) {
                log.info("dynamic-datasource initial loaded [{}] datasource,primary datasource named [{}]", dataSources.size(), primary);
            } else {
                throw new RuntimeException("dynamic-datasource Please check the setting of primary");
            }
        }
    
    }
    

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

    1. 通过数据源提供器获取所有的数据源,
    2. 将上一步获得的所有的数据源添加到 dataSourceMap 和 addGroupDataSource 中. 这里获取数据源的操作就完成
    3. 顺着这个思路, 如何添加到 dataSourceMap 和 addGroupDataSource中的呢?
    private void addGroupDataSource(String ds, DataSource dataSource) {
            if (ds.contains(UNDERLINE)) {
                String group = ds.split(UNDERLINE)[0];
                if (groupDataSources.containsKey(group)) {
                    groupDataSources.get(group).addDatasource(dataSource);
                } else {
                    try {
                        DynamicGroupDataSource groupDatasource = new DynamicGroupDataSource(group, strategy.newInstance());
                        groupDatasource.addDatasource(dataSource);
                        groupDataSources.put(group, groupDatasource);
                    } catch (Exception e) {
                        log.error("dynamic-datasource - add the datasource named [{}] error", ds, e);
                        dataSourceMap.remove(ds);
                    }
                }
            }
        }
    

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

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

    public final class DynamicDataSourceContextHolder {
    
        /**
         * 为什么要用链表存储(准确的是栈)
         * <pre>
         * 为了支持嵌套切换,如ABC三个service都是不同的数据源
         * 其中A的某个业务要调B的方法,B的方法需要调用C的方法。一级一级调用切换,形成了链。
         * 传统的只设置当前线程的方式不能满足此业务需求,必须使用栈,后进先出。
         * </pre>
         */
        private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {
            @Override
            protected Deque<String> initialValue() {
                return new ArrayDeque<>();
            }
        };
    
        private DynamicDataSourceContextHolder() {
        }
    
        /**
         * 获得当前线程数据源
         *
         * @return 数据源名称
         */
        public static String peek() {
            return LOOKUP_KEY_HOLDER.get().peek();
        }
    
        /**
         * 设置当前线程数据源
         * <p>
         * 如非必要不要手动调用,调用后确保最终清除
         * </p>
         *
         * @param ds 数据源名称
         */
        public static void push(String ds) {
            LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds) ? "" : ds);
        }
    
        /**
         * 清空当前线程数据源
         * <p>
         * 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称
         * </p>
         */
        public static void poll() {
            Deque<String> deque = LOOKUP_KEY_HOLDER.get();
            deque.poll();
            if (deque.isEmpty()) {
                LOOKUP_KEY_HOLDER.remove();
            }
        }
    
        /**
         * 强制清空本地线程
         * <p>
         * 防止内存泄漏,如手动调用了push可调用此方法确保清除
         * </p>
         */
        public static void clear() {
            LOOKUP_KEY_HOLDER.remove();
        }
    }
    

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

    九、总结

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



  • 相关阅读:
    Windows消息机制
    inherited 为什么可以调用父类的private函数? [问题点数:100分,结帖人:h2plus0]
    C++Buidler6中需要注意的几个问题
    BGA封装芯片拆装全程纪实
    Delphi组件开发教程指南(四)组件生成过程(TWinControl)
    Delphi技巧集六 (等待执行完一个外部程序再执行另一个程序)
    C++ Builder高级应用开发指南
    干掉“Spirale”病毒
    完全看懂新世代x86指令集結構
    Delphi 组件撰写常问问题delphi 在整合环境中如何找出组件所产生的问题
  • 原文地址:https://www.cnblogs.com/ITPower/p/15110531.html
Copyright © 2011-2022 走看看