接 SpringBoot 動態配置數據源-進階-可視化動態配置數據源-1
配置文件修改后,需要使配置生效
采用springcloud 配置 jar : spring-cloud-starter-config + spring-boot-starter-actuator
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> <version>2.0.5.RELEASE</version> </dependency>
application.yml 添加配置
management:
endpoints:
web:
exposure:
include: refresh
配置參數刷新
@Component @Getter @RefreshScope public class ApplicationConfig { @Value("${XXX.XXX}") private String XXX; @Autowired private Environment environment; }
動態數據源- AbstractRoutingDataSource(每執行一次數據庫,動態獲取DataSource)
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
/**
* 動態更新自定義數據源
* @param defaultDataSource
* @param customDataSources
*/
public void updateTargetDataSource(DataSource defaultDataSource,Map<String,DataSource> slaveDataSources){
Map<Object,Object> customDS=new HashMap<Object, Object>();
customDS.putAll(slaveDataSources);
setTargetDataSources(customDS);
setDefaultTargetDataSource(defaultDataSource);
afterPropertiesSet();
}
}
動態數據源通知-DynamicDataSourceAspect
@Aspect
@Order(-1)//保證在@Transactional之前執行
@Component
public class DynamicDataSourceAspect {
//改變數據源
public void changeDataSource( String targetDataSource) {
if (StringUtil.isNotEmpty(targetDataSource) &&
DynamicDataSourceContextHolder.isContainsDataSource(targetDataSource)) {
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource);
}
}
public void clearDataSource( String targetDataSource) {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
動態數據源上下文管理 -DynamicDataSourceContextHolder
public class DynamicDataSourceContextHolder {
//存放當前線程使用的數據源類型信息
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
//存放數據源id
public static Set<String> dataSourceIds = new HashSet<>();
//設置數據源
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
//獲取數據源
public static String getDataSourceType() {
return contextHolder.get();
}
//清除數據源
public static void clearDataSourceType() {
contextHolder.remove();
}
//判斷當前數據源是否存在
public static boolean isContainsDataSource(String dataSourceId) {
return dataSourceIds.contains(dataSourceId);
}
}
注冊動態數據源-初始化數據源和提供了執行動態切換數據源的工具類- EnvironmentAware(獲取配置文件配置的屬性值)
@Slf4j
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
@Override
public void setEnvironment(Environment environment) {
DynamicDataSourceRegisterUtil.initDefaultDataSource(environment);
DynamicDataSourceRegisterUtil.initSlaveDataSources(environment);
}
/**
* 初始化注冊數據源 BeanDefinition
* @param annotationMetadata
* @param beanDefinitionRegistry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
//添加默認數據源
targetDataSources.put(DynamicDataSourceRegisterUtil.DEFAULT_TARGET_DATA_SOURCE, DynamicDataSourceRegisterUtil.defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add(DynamicDataSourceRegisterUtil.DEFAULT_TARGET_DATA_SOURCE);
//添加其他數據源
targetDataSources.putAll(DynamicDataSourceRegisterUtil.slaveDataSources);
DynamicDataSourceContextHolder.dataSourceIds.addAll(DynamicDataSourceRegisterUtil.slaveDataSources.keySet());
//創建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue(DynamicDataSourceRegisterUtil.DEFAULT_TARGET_DATA_SOURCE, DynamicDataSourceRegisterUtil.defaultDataSource);
mpv.addPropertyValue(DynamicDataSourceRegisterUtil.TARGET_DATA_SOURCES, targetDataSources);
//注冊 - BeanDefinitionRegistry
beanDefinitionRegistry.registerBeanDefinition(
DynamicDataSourceRegisterUtil.DATA_SOURCE, beanDefinition);
log.info("Dynamic DataSource Registry");
}
}
數據源注冊工具類-DynamicDataSourceRegisterUtil 配合DynamicDataSourceRegister
@Slf4j
public class DynamicDataSourceRegisterUtil {
private static final ConversionService conversionService = new DefaultConversionService();
public final static String DEFAULT_TARGET_DATA_SOURCE ="defaultTargetDataSource";
//默認數據源
public static DataSource defaultDataSource;
//用戶自定義數據源
public static Map<String, DataSource> slaveDataSources = new HashMap<>();
public final static String TARGET_DATA_SOURCES ="targetDataSources";
public final static String DATA_SOURCE ="dataSource";
/**
* 創建DataSource
* @param dataSourceMap
* @return
*/
public static DataSource buildDataSource(Map<String, Object> dataSourceMap) {
DataSource dataSource=null;
try {
dataSource= DruidDataSourceFactory.createDataSource(dataSourceMap);
if(dataSource instanceof DruidDataSource){
//注意:這一設置是為解決Druid 在獲取連接時由於連接配置出錯會一直等待獲取連接,比較重要
((DruidDataSource) dataSource).setBreakAfterAcquireFailure(true);
}
return dataSource;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 初始化主數據源
*/
public static void initDefaultDataSource(Environment env) {
// 讀取主數據源
Map<String, Object> dsMap = new HashMap<>();
dsMap.put("driver", env.getProperty("spring.datasource.driver-class-name"));
dsMap.put("url", env.getProperty("spring.datasource.url"));
dsMap.put("username", env.getProperty("spring.datasource.username"));
dsMap.put("password", env.getProperty("spring.datasource.password"));
defaultDataSource = buildDataSource(dsMap);
}
/**
* 關閉老的數據源
*/
private static void closeOldCustomDataSources(){
if(slaveDataSources!=null&&slaveDataSources.size()>0){
for (String key:slaveDataSources.keySet()){
DataSource dataSource =slaveDataSources.get(key);
if(dataSource instanceof DruidDataSource){
((DruidDataSource)dataSource).close();
log.info("closed datasource "+key);
}
}
}
if(slaveDataSources!=null){
slaveDataSources.clear();
}
}
/**
* 初始化更多數據源
*
*/
public static void initSlaveDataSources(Environment env) {
//初始化之前要先將老的數據源關閉
closeOldCustomDataSources();
// 讀取配置文件獲取更多數據源,也可以通過defaultDataSource讀取數據庫獲取更多數據源
String dsPrefixs = env.getProperty("slave.datasource.names");
for (String dsPrefix : dsPrefixs.split(",")) {// 多個數據源
Map<String, Object> dsMap = new HashMap<>();
dsMap.put("driver", env.getProperty("slave.datasource." + dsPrefix + ".driver-class-name"));
dsMap.put("url", env.getProperty("slave.datasource." + dsPrefix + ".url"));
dsMap.put("username", env.getProperty("slave.datasource." + dsPrefix + ".username"));
dsMap.put("password", env.getProperty("slave.datasource." + dsPrefix + ".password"));
DataSource dataSource= buildDataSource(dsMap);
slaveDataSources.put(dsPrefix, dataSource);
}
}
/**
* 更新配置文件,並熱加載到Environment中
*/
public synchronized static void refreshDataSoureProperties(Environment environment) {
//將屬性持久化到配置文件
refreshDataSource(environment);
}
/**
* 更新配置之后要更新DynamicDataSource
* @param environment
*/
private static void refreshDataSource(Environment environment) {
initSlaveDataSources(environment);
DynamicDataSource dynamicDataSource =ApplicationContextUtil.getBean(DATA_SOURCE);
dynamicDataSource.updateTargetDataSource(defaultDataSource,slaveDataSources);
DynamicDataSourceContextHolder.dataSourceIds.clear();
DynamicDataSourceContextHolder.dataSourceIds.add(DEFAULT_TARGET_DATA_SOURCE);
DynamicDataSourceContextHolder.dataSourceIds.addAll(slaveDataSources.keySet());
}
}
springboot 啟動Application 增加注解引入
@Import({DynamicDataSourceRegister.class})
調用:
頁面調用接口修改Yaml文件配置項后
//將配置增加到上下文環境中-spring-cloud 包含的接口
POST:
http://127.0.0.1:8080/XXX/actuator/refresh
//返回后,調用自己的refresh-API 將數據源加載
@PostMapping("/refresh")
public JSONObject refresh(){
//數據源更新
DynamicDataSourceRegisterUtil.refreshDataSoureProperties(configCommon.getEnvironment());
//TODO 其他業務代碼
JSONObject result = new JSONObject();
result.put("code",200);
result.put("msg","初始化配置成功");
return result;
}
也可以自己封裝一下 Java-http調用 /actuator/refresh,再更新數據源配置
完結
-----------------------20191230----------------------------------
