充满元气的java爱好者 潜水
  • 1发帖数
  • 1主题数
  • 0关注数
  • 0粉丝
开启左侧

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

[复制链接]
一、为什么要研究Spring动态多数据源

期初,最开始的缘故原由是:想将答题服务中发送主观题答题数据给修正中心件这块抽象出来, 但这块重要使用的是mq消息的方式发送到修正中心件,所以,末了决定将mq进行抽象,抽象后的结果是:语文,英语,通用使命都能个性化的设置mq,且可以扩展到任何使用mq的业务场景上。终端需要做的就是增长mq设置,自定义消费者业务逻辑方法,调用send方法即可。
这样做的好处是:原本在每个使用到mq的项目里都要写一遍mq生产者,mq消费者,发送mq数据,监听mq消费等动作,且如果一个项目里有多个mq设置,要写多遍这样的设置。抽象后,只需要设置文件中进行设置,然后自定义个性化的业务逻辑消费者,就可以进行mq发送了。
这样一个可动态设置的mq,要求照旧挺多的,怎样动态设置? 怎样能够在服务器启动的时候就启动n个mq的生产者和消费者? 发送数据的时候, 怎么找到精确的mq发送呢?
其实, 我一直相信, 我碰到的题目, 肯定有大神已经碰到过, 并且已经有了成熟的办理方案了. 于是, 开始搜索行业内的办理方案, 找了很久也没找到,末了在同事的提示下,发现Spring动态设置多数据源的头脑和我想实现的动态设置多MQ的头脑类似。于是,我开始花时间研究Spring动态多数据源的源码。
二、Spring动态多数据源框架梳理

2.1 框架布局

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

                               
登录/注册后可看大图

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

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

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

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

                               
登录/注册后可看大图

上图是Spring动态多数据源的源码项目布局,我们重要列一下重要的布局
----annotation:定义了DS主机----aop:定义了一个前置通知,切面类----creator:动态多数据源的创建器----exception:非常处理惩罚----matcher:匹配器----processor:ds处理惩罚器----provider:数据员提供者----spring:spring动态多数据源启动设置相关类----toolkit:工具包----AbstractRoutingDataSource:动态路由数据源抽象类----DynamicRoutingDataSource:动态路由数据源实现类2.3 整体项目布局图

下图是Spring多态多数据源的代码项目布局图。

                               
登录/注册后可看大图

这个图内容比较多,所以字比较小,大概看出一共有6个部分就可以了。后面会就每一个部分详细分析。
三、项目源码分析

3.1 引入Spring依赖jar包.

Spring动态多数据源,我们在使用的时候,直接引入jar,然后设置数据源就可以使用了。设置jar包如下
            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文件的重要内容。

                               
登录/注册后可看大图

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

                               
登录/注册后可看大图

上图是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。
以上就是对数据源处理惩罚器模块的的分析,那么最终在哪里被调用呢?来看下一个模块。
五、动态数据源注解通知模块

这一块对应的源代码布局如下:

                               
登录/注册后可看大图

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

  • 切面类:DynamicDataSourceAdvisor,DynamicDataSourceAnnotationAdvisor
  • 切点类:DynamicAspectJExpressionPointcut,DynamicJdkRegexpMethodPointcut
  • 前置通知类: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@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

精彩评论3

Deathef 发表于 2021-8-7 15:10:03 | 显示全部楼层
这个开源库并不好用。第一不支持运行时增加读库,这一点就会被运维部直接pass了。第二不支持多租户,这个就会被做SaaS业务的直接pass了。
Marianas 发表于 2021-8-7 14:26:08 | 显示全部楼层
转发了
且行且珍惜42960936 发表于 2021-8-7 13:41:33 | 显示全部楼层
转发了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

猜你喜欢
在线客服邮箱
wxcy#wkgb.net

邮箱地址#换为@

Powered by 创意电子 ©2018-现在 专注资源实战分享源码下载站联盟商城