五、动态数据源注解通知模块
这一块对应的源代码结构如下:
这个模块里主要有三部分:
- 切面类: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 @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 \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<? 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是父类,其他类继承自这个类,下面来看一下他们的结构
四个基本的数据源类,最后通过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 \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<? 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; }
这个文件的功能:
- 这个文件定义了扫描yml配置文件的属性前缀:spring.datasource.dynamic,
- 设置了默认的数据库是master主库, strict表示是否严格模式: 如果是严格模式,那么没有配置数据库,却调用了会抛异常, 如果非严格模式, 没有配数据库, 会采用默认的主数据库.
- datasource: 用来存储读取到的数据源, 可能有多个数据源, 所以是map的格式
- strategy: 这里定义了负载均衡策略, 采用的是策略设计模式: 可以在配置文件中定义, 如果有多个数据源匹配,如何选择. 可选方案: 1. 负载均衡策略, 2. 随机策略.
其他参数就不多说, 比较简单, 见名思意. 以上就是数据源提供者的主要内容了.
八、动态路由数据源
这一块主要功能是在调用的时候, 进行动态选择数据源。其源代码结构如下图
我们知道动态数据源可以嵌套,为什么可以嵌套呢,就是这里决定的, 这里一共有四个文件,
1.AbstractRoutingDataSource: 抽象的路由数据源, 这个类主要作用是在找到目标数据源的情况下,连接数据库.
2.DynamicGroupDataSource:动态分组数据源, 在一个请求链接下的所有数据源就是一组. 也就是一个请求过来, 可以嵌套数据源, 这样数据源就有多个, 这多个就是一组.
- DynamicRoutingDataSource: 动态路由数据源, 第一类AbstractRoutingDataSource用来连接数据源,那么到底应该链接哪个数据源呢?在这个类里面查找, 如何找呢, 从DynamicDataSourceContextHolder里面获取当前线程的数据源. 然后链接数据库.
- 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: 动态分组数据源,
这里定义了分组的概念.
- 每一个组有一个组名
- 组里面有多个数据源, 用list存储,指的注意的是, list是一个LinkedList,有顺序的, 因为在调用数据库查询数据的时候, 不能调混了,所以使用顺序列表集合.
- 选择数据源的策略, 有多个数据源,按照什么策略选择呢?由策略类型来决定.
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()方法这么重要, 就来看看他主要做了哪些事情吧.
- 通过数据源提供器获取所有的数据源,
- 将上一步获得的所有的数据源添加到 dataSourceMap 和 addGroupDataSource 中. 这里获取数据源的操作就完成
- 顺着这个思路, 如何添加到 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, 保证每个线程获取的是当前线程的数据源.
九、总结
以上就是整个数据源源码的全部内容, 内容比较多, 部分功能描述不是特别详细. 如有任何疑问, 可以留言, 一起研究.