前言
最近需要实现一个功能,动态刷新线上数据源环境,下面来使用Apollo配置中心和Spring提供的AbstractRoutingDataSource来实现。
具体实现
Apollo是携程开源的统一配置中心,和springboot无缝衔接并且不需要安装其他软件就可以直接使用,可以实时推送最新的配置文件。Spring提供的AbstractRoutingDataSource用于动态管理数据源,可以动态更新数据源,一般数据库的读写分离也是用这个抽象类实现的。
对Apollo不熟悉的可以先了解一下,GitHub:https://github.com/ctripcorp/apollo
关于AbstractRoutingDataSource,介绍一下我们用到的方法
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { //传入的数据源 private Map<Object, Object> targetDataSources; //拿着子类实现的determineCurrentLookupKey()方法的返回值当做key在这个Map中寻找数据源 private Map<Object, DataSource> resolvedDataSources; //放入多个数据源 public void setTargetDataSources(Map<Object, Object> targetDataSources) { this.targetDataSources = targetDataSources; } //属性设置完成后执行 public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } else { this.resolvedDataSources = new HashMap(this.targetDataSources.size()); Iterator var1 = this.targetDataSources.entrySet().iterator(); while(var1.hasNext()) { Entry<Object, Object> entry = (Entry)var1.next(); Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue()); this.resolvedDataSources.put(lookupKey, dataSource); } if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource); } } } protected Object resolveSpecifiedLookupKey(Object lookupKey) { return lookupKey; } //子类要实现的抽象方法,数据源的获取策略 protected abstract Object determineCurrentLookupKey(); }
下面来实现通过Apollo动态修改数据源:
@Configuration public class DataSourceConfiguration { private final static String DATASOURCE_TAG = "db"; @Autowired ApplicationContext context; @ApolloConfig Config config; @Bean("dataSource") public DynamicDataSource dynamicDataSource() {
//使用springboot默认的连接池 DynamicDataSource source = new DynamicDataSource(); //只有一个数据源,传入的Map的key为db,value为使用的数据源 source.setTargetDataSources(Collections.singletonMap(DATASOURCE_TAG, dataSource())); return source; } //Apollo监听配置是否修改 @ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent) { SetchangedKeys = changeEvent.changedKeys(); if (changedKeys.contains("spring.datasource.url")) { DynamicDataSource source = context.getBean(DynamicDataSource.class); //当检测到数据库地址改变时,重新设置数据源 source.setTargetDataSources(Collections.singletonMap(DATASOURCE_TAG, dataSource())); //调用该方法刷新resolvedDataSources,下次获取数据源时将获取到新设置的数据源 source.afterPropertiesSet(); } } public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(config.getProperty("spring.datasource.url", "")); dataSource.setUsername(config.getProperty("spring.datasource.username", "")); dataSource.setPassword(config.getProperty("spring.datasource.password", "")); return dataSource; } //简单实现AbstractRoutingDataSource,因为只是有一个数据源,所以任何时候选择的数据源都一样 class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DATASOURCE_TAG; } } }
determineCurrentLookupKey