解析配置文件自动装配 DataSource + AbstractRoutingDataSource + AOP 实现动态数据源 上:原理解析,解析数据源

spring boot 自动装配会通过 spring.datasource.*为我们自动装配数据源,所以想要动态的切换数据源,第一件事是配置数据源,其次是怎么切换?最后何时切换?

原理解析(使用 AbstractRoutingDataSource 实现)

spring-jdbc 提供了 AbstractRoutingDataSourcegetConnection() 时通过 lookup key决定目标数据源,使用 AbstractRoutingDataSource 需要准备至少两个数据源,这在源码中也有体现:

解析配置文件自动装配 DataSource + AbstractRoutingDataSource + AOP 实现动态数据源 上:原理解析,解析数据源_第1张图片

一个默认数据源 + 动态匹配的数据源, resolvedDataSources 是一个 Object 为 key, DataSource为 value 的 Map,可见这里 key 即充当了 lookup key的角色。
解析配置文件自动装配 DataSource + AbstractRoutingDataSource + AOP 实现动态数据源 上:原理解析,解析数据源_第2张图片
image.png

在调用 getConnection 获取数据源时会调用 determineTargetDataSource 方法获取目标数据源,进而通过 determineCurrentLookupKey 方法获得当前的 lookup key,再从 resolvedDataSources 中获得目标数据源。

AbstractRoutingDataSource 是一个抽象类,determineCurrentLookupKey是其唯一的抽象方法,意味着子类只需在适当的时候修改当前的 lookup key 就能实现动态的更改当前的数据源。

配置数据源

配置数据源也就是给 AbstractRoutingDataSourcedefaultTargetDataSourceresolvedDataSources 进行赋值。

这里我们将数据源配置在 properties 文件中,通过工具类解析进行配置。

在 properties 中配置数据源

demo 项目为 spring boot 项目,配置文件采用 yml 格式:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
    username: root
    password: 1234
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    multi:
      ds-keys: db1,db2
      db1:
        url: jdbc:mysql://127.0.0.1:3306/db11?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 1234
        driver-class-name: com.mysql.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
      db2:
        url: jdbc:mysql://127.0.0.1:3306/db12?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 1234
        driver-class-name: com.mysql.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource

spring.datasource 前缀的将被解析为默认数据源, spring.datasource.multi 前缀的则作为动态数据源进行解析。
spring.datasource.multi.ds-keys 是比较关键的属性,这个参数定义了项目中所有的动态数据源的 key,如上述代码所示,spring.datasource.multi.ds-keys: db1,db2:表明共有两个动态数据源,key 分别为 db1 和 db2 ,进行解析时将解析如下两个前缀配置的数据源:

  • spring.datasource.multi.db1
  • spring.datasource.multi.db2

当然这个规则是我自己定义的,可以按需定义自己的规则并实现相应的解析。

解析数据源

使用 DynamicDataSourceBuilder 类来解析数据源,注册为 bean 并实现 EnvironmentAware 接口,在 setEnvironment 方法中开始进行解析。

解析配置文件自动装配 DataSource + AbstractRoutingDataSource + AOP 实现动态数据源 上:原理解析,解析数据源_第3张图片
image.png

initDefaultDataSourceinitCustomDataSources 方法将解析得到默认数据源和动态数据源,默认数据源将赋值给 defaultDataSourcetargetDataSources 中是所有解析得到的数据源,包括默认数据源,其数据类型是一个 Map,Map 的 key 即为 Lookup key

初始化动态数据源

    /**
     * 初始化定制数据源
     */
    private void initCustomDataSources(Environment env) {

        // 读取配置文件获取定制数据源,也可以通过数据库获取数据源
        String dsNames = env.getProperty(customDataSourceKeys);

        for (String dsKey : dsNames.split(",")) {
            DataSource ds = buildDataSource(env, customDataSourcePrefix + "." + dsKey);
            targetDataSources.put(dsKey, ds);
            dataBinder(ds, env);
        }

    }

customDataSourceKeys 即为 spring.datasource.multi.ds-keys,得到定制数据源前缀后进行构建,之后添加到 targetDataSources 中。

构建数据源


    /**
     * 创建 datasource.
     */
    @SuppressWarnings("unchecked")
    private DataSource buildDataSource(Environment env, String dsPrefix) {

        try {

            String prefix = dsPrefix + ".";
            String dbpType = env.getProperty(prefix + DataSourcePropertyKey.type, defaultDataSourceType);
            Class dsType = (Class) Class.forName(dbpType);

            DataSource dataSource = DataSourceBuilder.create()
                    .driverClassName(env.getProperty(prefix + DataSourcePropertyKey.driverClassName))
                    .url(env.getProperty(prefix + DataSourcePropertyKey.url))
                    .username(env.getProperty(prefix + DataSourcePropertyKey.username))
                    .password(env.getProperty(prefix + DataSourcePropertyKey.password))
                    .type(dsType)
                    .build();

            configDataSourcePool(dataSource);
            return dataSource;

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

    }

数据源的构建通过读取 properties 文件,并借助 DataSourceBuilder 类进行构建。configDataSourcePool 方法可对数据源对应的连接池进行配置。

这里需要注意的是 spring boot 2.* 对配置文件的读取 API 有比较大的变动,可参考这里。

你可能感兴趣的:(解析配置文件自动装配 DataSource + AbstractRoutingDataSource + AOP 实现动态数据源 上:原理解析,解析数据源)