feat: 动态更新配置项,测试OK
This commit is contained in:
parent
7ebe2b3b69
commit
3ce36ef0cb
|
@ -5,7 +5,6 @@ import cn.hutool.core.thread.ThreadUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.odboy.config.model.msgtype.ClientInfo;
|
import cn.odboy.config.model.msgtype.ClientInfo;
|
||||||
import cn.odboy.config.netty.ConfigClient;
|
import cn.odboy.config.netty.ConfigClient;
|
||||||
import cn.odboy.config.util.PropertyNameUtil;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -69,6 +68,9 @@ public class ClientConfigLoader {
|
||||||
/** Mac路径分割符 */
|
/** Mac路径分割符 */
|
||||||
private static final String DEFAULT_PATH_SEP_MAC = "/";
|
private static final String DEFAULT_PATH_SEP_MAC = "/";
|
||||||
|
|
||||||
|
/** 配置源名称 */
|
||||||
|
public static final String PROPERTY_SOURCE_NAME = "kenaito-dynamic-config";
|
||||||
|
|
||||||
/** 当前客户端配置 */
|
/** 当前客户端配置 */
|
||||||
public static final ClientInfo clientInfo = new ClientInfo();
|
public static final ClientInfo clientInfo = new ClientInfo();
|
||||||
|
|
||||||
|
@ -84,6 +86,9 @@ public class ClientConfigLoader {
|
||||||
/** 转换后的配置信息:filename -> {configKey: configValue} */
|
/** 转换后的配置信息:filename -> {configKey: configValue} */
|
||||||
public static Map<String, Map<String, Object>> lastConfigs = new HashMap<>();
|
public static Map<String, Map<String, Object>> lastConfigs = new HashMap<>();
|
||||||
|
|
||||||
|
/** 所有自定义配置项缓存 */
|
||||||
|
public static Map<String, Object> cacheConfigs = new HashMap<>();
|
||||||
|
|
||||||
/** 定时将配置写盘,缓存配置信息 */
|
/** 定时将配置写盘,缓存配置信息 */
|
||||||
private final Thread fixedTimeFlushConfigFileThread =
|
private final Thread fixedTimeFlushConfigFileThread =
|
||||||
ThreadUtil.newThread(
|
ThreadUtil.newThread(
|
||||||
|
@ -201,13 +206,15 @@ public class ClientConfigLoader {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
// 合并配置项
|
||||||
|
cacheConfigs.clear();
|
||||||
Set<Map.Entry<String, Map<String, Object>>> filename2ConfigMap = lastConfigs.entrySet();
|
Set<Map.Entry<String, Map<String, Object>>> filename2ConfigMap = lastConfigs.entrySet();
|
||||||
for (Map.Entry<String, Map<String, Object>> filename2Config : filename2ConfigMap) {
|
for (Map.Entry<String, Map<String, Object>> filename2Config : filename2ConfigMap) {
|
||||||
MapPropertySource propertySource =
|
cacheConfigs.putAll(filename2Config.getValue());
|
||||||
new MapPropertySource(
|
|
||||||
PropertyNameUtil.get(filename2Config.getKey()), filename2Config.getValue());
|
|
||||||
environment.getPropertySources().addFirst(propertySource);
|
|
||||||
}
|
}
|
||||||
|
MapPropertySource propertySource =
|
||||||
|
new MapPropertySource(PROPERTY_SOURCE_NAME, cacheConfigs);
|
||||||
|
environment.getPropertySources().addFirst(propertySource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package cn.odboy.config.context;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
|
import org.springframework.core.env.MapPropertySource;
|
||||||
|
import org.springframework.core.env.MutablePropertySources;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端配置 辅助类
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2024-12-07
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ClientPropertyHelper {
|
||||||
|
private final ConfigurableEnvironment environment;
|
||||||
|
private final ValueAnnotationProcessor valueAnnotationProcessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态更新配置值
|
||||||
|
*
|
||||||
|
* @param propertyName 属性路径名
|
||||||
|
* @param value 属性值
|
||||||
|
*/
|
||||||
|
public void updateValue(String propertyName, Object value) {
|
||||||
|
if (StrUtil.isNotBlank(propertyName)) {
|
||||||
|
// 设置属性值
|
||||||
|
MutablePropertySources propertySources = environment.getPropertySources();
|
||||||
|
if (propertySources.contains(ClientConfigLoader.PROPERTY_SOURCE_NAME)) {
|
||||||
|
// 更新属性值
|
||||||
|
PropertySource<?> propertySource =
|
||||||
|
propertySources.get(ClientConfigLoader.PROPERTY_SOURCE_NAME);
|
||||||
|
Map<String, Object> source = ((MapPropertySource) propertySource).getSource();
|
||||||
|
source.put(propertyName, value);
|
||||||
|
}
|
||||||
|
// 单独更新@Value对应的值
|
||||||
|
valueAnnotationProcessor.setValue(propertyName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,39 +11,37 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载并处理@Value对应的引用
|
* 加载并处理@Value对应的引用, @ConfigurationProperties注解下的值会自动刷新所以不用管
|
||||||
*
|
*
|
||||||
* @author odboy
|
* @author odboy
|
||||||
* @date 2024-12-07
|
* @date 2024-12-07
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class ValueAnnotationBeanPostProcessor implements BeanPostProcessor {
|
public class ValueAnnotationProcessor implements BeanPostProcessor {
|
||||||
private final Logger logger = LoggerFactory.getLogger(ValueAnnotationBeanPostProcessor.class);
|
private final Logger logger = LoggerFactory.getLogger(ValueAnnotationProcessor.class);
|
||||||
private final Map<String, Field> nameFieldMap = new HashMap<>();
|
private final Map<String, Field> valueFieldMap = new HashMap<>();
|
||||||
private final Map<String, Object> nameBeanMap = new HashMap<>();
|
private final Map<String, Object> valueBeanMap = new HashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||||
Class<?> clazz = bean.getClass();
|
Class<?> clazz = bean.getClass();
|
||||||
while (clazz != null) {
|
while (clazz != null) {
|
||||||
|
// 处理@Value
|
||||||
for (Field field : clazz.getDeclaredFields()) {
|
for (Field field : clazz.getDeclaredFields()) {
|
||||||
if (field.isAnnotationPresent(Value.class)) {
|
if (field.isAnnotationPresent(Value.class)) {
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
// 对@Value的处理
|
// 对@Value的处理
|
||||||
Value annotation = field.getAnnotation(Value.class);
|
Value annotation = field.getAnnotation(Value.class);
|
||||||
String propertyName = annotation.value();
|
String propertyName = annotation.value();
|
||||||
String substring =
|
propertyName =
|
||||||
propertyName.substring(propertyName.indexOf("${") + 2, propertyName.lastIndexOf("}"));
|
propertyName.substring(propertyName.indexOf("${") + 2, propertyName.lastIndexOf("}"));
|
||||||
// 处理带有默认值的配置
|
// 处理带有默认值的配置
|
||||||
propertyName = substring;
|
if (propertyName.contains(":")) {
|
||||||
if (substring.contains(":")) {
|
int firstSpIndex = propertyName.indexOf(":");
|
||||||
String[] splits = substring.split(":");
|
propertyName = propertyName.substring(0, firstSpIndex);
|
||||||
if (splits.length == 2) {
|
|
||||||
propertyName = splits[0];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
nameFieldMap.put(propertyName, field);
|
valueFieldMap.put(propertyName, field);
|
||||||
nameBeanMap.put(propertyName, bean);
|
valueBeanMap.put(propertyName, bean);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clazz = clazz.getSuperclass();
|
clazz = clazz.getSuperclass();
|
||||||
|
@ -53,13 +51,13 @@ public class ValueAnnotationBeanPostProcessor implements BeanPostProcessor {
|
||||||
|
|
||||||
public void setValue(String propertyName, Object value) {
|
public void setValue(String propertyName, Object value) {
|
||||||
try {
|
try {
|
||||||
Field field = nameFieldMap.getOrDefault(propertyName, null);
|
Field field = valueFieldMap.getOrDefault(propertyName, null);
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
field.set(nameBeanMap.get(propertyName), value);
|
field.set(valueBeanMap.get(propertyName), value);
|
||||||
}
|
}
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
logger.error("配置 {} 字段值 {} 失败", propertyName, value, e);
|
logger.error("设置 {} 字段值 {} 失败", propertyName, value, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package cn.odboy.rest;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册中心配置
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2024-12-07
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "kenaito.config-center")
|
||||||
|
public class ConfigCenterProperties {
|
||||||
|
private String server;
|
||||||
|
private Integer port;
|
||||||
|
private String dataId;
|
||||||
|
private String env;
|
||||||
|
private String cacheDir;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package cn.odboy.rest;
|
package cn.odboy.rest;
|
||||||
|
|
||||||
import cn.odboy.config.context.ValueAnnotationBeanPostProcessor;
|
import cn.odboy.config.context.ClientPropertyHelper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
@ -16,19 +16,20 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/updateConfig")
|
@RequestMapping("/updateConfig")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class DemoController {
|
public class DemoController {
|
||||||
@Value("${kenaito.config-center.demo:123}")
|
@Value("${kenaito.config-center.test}")
|
||||||
private String demoStr;
|
private String testStr;
|
||||||
|
|
||||||
@Autowired private ValueAnnotationBeanPostProcessor valueAnnotationBeanPostProcessor;
|
private final ClientPropertyHelper clientPropertyHelper;
|
||||||
|
|
||||||
/** 配置变化了 */
|
/** 配置变化了 */
|
||||||
@GetMapping("/test")
|
@GetMapping("/test")
|
||||||
public ResponseEntity<Object> test() {
|
public ResponseEntity<Object> test() {
|
||||||
System.err.println("demoStr=" + demoStr);
|
System.err.println("testStr=" + testStr);
|
||||||
String propertyName = "kenaito.config-center.demo";
|
String propertyName = "kenaito.config-center.test";
|
||||||
valueAnnotationBeanPostProcessor.setValue(propertyName, "xxxxxxxxxx");
|
clientPropertyHelper.updateValue(propertyName, "Hello World");
|
||||||
System.err.println("demoStr=" + demoStr);
|
System.err.println("testStr=" + testStr);
|
||||||
return ResponseEntity.ok("success");
|
return ResponseEntity.ok("success");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue