一、为什么要研究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 |