AbstractRoutingDataSource源码分析

AbstractRoutingDataSource(源码)如何实现数据源动态切换

1.AbstractRoutingDataSource这个抽象类是继承了AbstractDataSource抽象类,并且实现了InitializingBean接口。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

2.InitializingBean接口提供了一个afterPropertiesSet()方法,凡是继承了该接口的类那么在初始化Bean的时候就会执行该方法。所以AbstractRoutingDataSource类中的afterPropertiesSet正是用于初始化一些信息,这个我们稍后会分析。

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

3.之前提到AbstractRoutingDataSource还继承了抽象类AbstractDataSource,而AbstractDataSource抽象类又继承了DataSource接口,在DataSource中只有两个方法,这两个方法会在AbstractDataSource抽象类中得到具体实现。

Connection getConnection() throws SQLException;
Connection getConnection(String var1, String var2) throws SQLException;

4.前三步我们已经将AbstractDataSource的关系简单的梳理了一下,现在我们结合刚刚梳理的关系来看AbstractDataSource的源码。

@Nullable
    private Map<Object, Object> targetDataSources;  目标数据源,也就是我们要切换的多个数据源都存放到这里
    @Nullable
    private Object defaultTargetDataSource; 默认数据源,如果想指定默认数据源,可以给它赋值
    private boolean lenientFallback = true;
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;存放真正的数据源信息,将targetDataSources的信息copy一份
    @Nullable
    private DataSource resolvedDefaultDataSource; 默认数据源,将defaultTargetDataSource转为DataSource赋值给resolvedDefaultDataSource

    public AbstractRoutingDataSource() {
    }

5.了解了上面一些字段的具体作用,现在我们来看AbstractRoutingDataSource抽象类中afterPropertiesSet()方法到底做了哪些初始化操作

    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            this.resolvedDataSources = new HashMap(this.targetDataSources.size());
            this.targetDataSources.forEach((key, value) -> {
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                this.resolvedDataSources.put(lookupKey, dataSource);
            });
            if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }

        }
    }

5.1 可以看到当targetDataSources为空时,会抛出IllegalArgumentException错误,所以我们在配置多数据源时,至少需要传入一个数据源。

 if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        }

5.2 初始化了resolvedDataSources大小,正如刚刚所说,resolvedDataSources只是把targetDataSources的内容copy了一份,不同之处在于targetDataSources的value是Object类型,而resolvedDataSources的value是DataSource类型。

 this.resolvedDataSources = new HashMap(this.targetDataSources.size());

5.3遍历targetDataSources集合,然后调用了resolveSpecifiedLookupKey()方法和resolveSpecifiedDataSource()方法,最后将返回值当作Key-Value放入resolvedDataSources集合中。

            this.targetDataSources.forEach((key, value) -> {
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                this.resolvedDataSources.put(lookupKey, dataSource);
            });

5.4那么方法resolveSpecifiedLookupKey()和方法resolveSpecifiedDataSource()分别做了什么呢?我们可以查看两个方法的实现。其实可以看到resolveSpecifiedLookupKey并没有做什么操作就直接返回了值,而resolveSpecifiedDataSource只是把Object转为DataSource对象返回。

protected Object resolveSpecifiedLookupKey(Object lookupKey) {
        return lookupKey;
    }

    protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
        if (dataSource instanceof DataSource) {
            return (DataSource)dataSource;
        } else if (dataSource instanceof String) {
            return this.dataSourceLookup.getDataSource((String)dataSource);
        } else {
            throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
        }
    }
    

6.所以afterPropertiesSet()初始话就是将targetDataSources的内容转化一下放到resolvedDataSources中,将defaultTargetDataSource转为DataSource赋值给resolvedDefaultDataSource。这个逻辑还是很简单的。

7.此时我们再看getConnection()方法是如何实现的。调用了determineTargetDataSource()方法。

public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }

8.那么determineTargetDataSource方法到底做了什么,我们可以看一下determineTargetDataSource的实现。该方法返回DataSource类型的对象,并且调用了determineCurrentLookupKey方法,可能这时有人发现了这个方法就是我们自定义数据源类要实现的那个方法。determineCurrentLookupKey返回一个Object,命名为lookupKey,将lookupKey作为key,到resolvedDataSources集合中去拿数据源,如果没有拿到数据源,那么它会拿默认数据源resolvedDefaultDataSource。如果还是没有拿到,此时就报错啦!

  protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }

        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            return dataSource;
        }
    }

这大概就是AbstractRoutingDataSource的实现原理了,源码只有100多行,可以认真的去理解一下。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
AbstractRoutingDataSource是一个用于多数据动态切换的核心逻辑。它通过将数据动态织入到程序中,实现了数据的灵活切换。使用AbstractRoutingDataSource可以实现读写分离,但是无法动态增加数据。\[1\] 在AbstractRoutingDataSource中,getConnection()方法根据查找lookup key键对不同目标数据进行调用。通常情况下,这个lookup key是通过线程绑定的事务上下文来实现的,但不一定如此。\[2\] DynamicDataSource是继承自AbstractRoutingDataSource的一个类,它实现了determineCurrentLookupKey()方法。这个方法用于确定当前的lookup key,即当前使用的数据类型。通过继承AbstractRoutingDataSource并实现determineCurrentLookupKey()方法,我们可以自定义数据的选择逻辑。\[3\] #### 引用[.reference_title] - *1* *2* [spring boot使用AbstractRoutingDataSource实现动态数据切换](https://blog.csdn.net/qq_37502106/article/details/91044952)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [spring AbstractRoutingDataSource详解](https://blog.csdn.net/u011463444/article/details/72842500)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值