feat: 动态更新配置项,测试OK

This commit is contained in:
骑着蜗牛追导弹 2024-12-07 14:25:11 +08:00
parent 7ebe2b3b69
commit 3ce36ef0cb
6 changed files with 107 additions and 32 deletions

View File

@ -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);
} }
} }
}; };

View File

@ -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);
}
}
}

View File

@ -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);
} }
} }
} }

View File

@ -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;
}

View File

@ -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");
} }
} }