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多行,可以认真的去理解一下。