一、问题场景描述
在使用MyBatis-Plus的DynamicRoutingDataSource时遇到的问题,当我在配置中心动态增加或者删除了一个数据源,他并不会自动同步最新的数据源,导致我用DynamicDataSourceContextHolder.push(ds)方法的时候拿不到刚添加的数据源
二、问题产生的原因
在Spring Cloud中刷新Bean,官方提供了@RefreshScope注解用于Bean的刷新,然而DynamicDataSourceProperties并没有该注解,而且dynamic-datasource-spring-boot-starter并没有实现获取刷新后的配置重新加入DynamicRoutingDataSource
三、源码分析
dynamic-datasource-spring-boot-starter的核心自动配置类为DynamicDataSourceAutoConfiguration,在该自动配置类中会初始化DynamicRoutingDataSource Bean对象,该对象实现了InitializingBean方法:
@Override
public void afterPropertiesSet() throws Exception {
// 检查开启了配置但没有相关依赖
checkEnv();
// 添加并分组数据源
Map dataSources = new HashMap<>(16);
for (DynamicDataSourceProvider provider : providers) {
dataSources.putAll(provider.loadDataSources());
}
for (Map.Entry 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 {
log.warn("dynamic-datasource initial loaded [{}] datasource,Please add your primary datasource or check your configuration", dataSources.size());
}
}
可以看到在此处调用了addDataSource方法
public synchronized void addDataSource(String ds, DataSource dataSource) {
DataSource oldDataSource = dataSourceMap.put(ds, dataSource);
// 新数据源添加到分组
this.addGroupDataSource(ds, dataSource);
// 关闭老的数据源
if (oldDataSource != null) {
closeDataSource(ds, oldDataSource);
}
log.info("dynamic-datasource - add a datasource named [{}] success", ds);
}
/**
* 新数据源添加到分组
*
* @param ds 新数据源的名字
* @param dataSource 新数据源
*/
private void addGroupDataSource(String ds, DataSource dataSource) {
if (ds.contains(UNDERLINE)) {
String group = ds.split(UNDERLINE)[0];
GroupDataSource groupDataSource = groupDataSources.get(group);
if (groupDataSource == null) {
try {
groupDataSource = new GroupDataSource(group, strategy.getDeclaredConstructor().newInstance());
groupDataSources.put(group, groupDataSource);
} catch (Exception e) {
throw new RuntimeException("dynamic-datasource - add the datasource named " + ds + " error", e);
}
}
groupDataSource.addDatasource(ds, dataSource);
}
}
除此之外,别的地方再也找不到调用该方法的源码了,至此可以得出结论,DynamicRoutingDataSource只有在程序刚启动的时候会读取配置中心的配置并加入到数据源Map中,此后新加入的数据源,或者删掉某一个数据源并不会生效
四、解决思路
既然官方没有实现自动刷新,那自己构造一个能刷新的配置不就行了吗?代码如下:
@Data
@RefreshScope
@ConfigurationProperties(DynamicDataSourceProperties.PREFIX)
public class RefreshableDynamicDataSourceProperties implements DisposableBean {
/**
* 每一个数据源
*/
private Map datasource = new LinkedHashMap<>();
@Override
public void destroy() throws Exception {
datasource = new LinkedHashMap<>();
}
}
注:此处一定要实现DisposableBean接口,并重置数据源,否则在配置中心删除数据源时没法正常删除,这是由于Spring源码中判断Bean的属性如果是复合类型如Map做的操作是putAll()操作,也就是说不会删除原来配置。
上面只是完成了第一步,下面还要将咱们自己的配置在配置刷新的时候加入到DynamicRoutingDataSource中,那么此时需要一个监听器去监听配置刷新的事件,代码如下:
@Order(0)
@Configuration
@RequiredArgsConstructor
@ConditionalOnClass(DynamicDataSourceAutoConfiguration.class)
@EnableConfigurationProperties(RefreshableDynamicDataSourceProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, DynamicDataSourceAutoConfiguration.class})
public class DynamicDataSourceConfig implements ApplicationListener {
private final RefreshableDynamicDataSourceProperties properties;
private final DataSource dataSource;
private final DefaultDataSourceCreator creator;
@Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
//获取最新的数据源
Map datasource = properties.getDatasource();
//获取原来的数据源
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
//移除当前数据源中不存在的数据源
Set keys = datasource.keySet();
//判断是否存在(不存在即删除)
ds.getDataSources().entrySet().removeIf(next -> !keys.contains(next.getKey()));
//添加新的数据源
datasource.forEach((key, value) -> {
ds.addDataSource(key, creator.createDataSource(value));
});
}
}
此时就实现了不重启工程的情况下动态应用配置中心数据源了。
五、总结
官方没有实现自动刷新可能有自己的考量吧,这里我主要记录一下自己的解决思路和方案,毕竟技术是随着需求去应用的,而不是为了技术而技术。