前言:在工作中需要实现动态刷新@Value字段值,但是项目使用的是Spring boot,又不想接入Nacos/Apollo等配置中心组件(额外增加成本),并且项目中只是简单的使用@Value读取一些配置信息进行使用(或者判断状态),所以写了简单的一个刷新机制,通过接口刷新@Value值,有需要的可以直接使用
一、自定义注解,用来需要修饰需要支持动态刷新的场景
import java.lang.annotation.*;
import java.lang.annotation.*;
/**
* @annotationName RefreshValue
* @description 自定义注解,用来需要修饰需要支持动态刷新的场景
**/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RefreshValue {
}
二、定义一个工具类实现配置转换
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @className RefreshEnvironment
* @description 定义一个工具类实现配置转换
**/
@Component
public class RefreshEnvironment implements EnvironmentAware {
private static ConfigurableEnvironment environment;
public static void updateValue(String key, Object newValue){
//自定的义配置文件名称
MutablePropertySources propertySources1 = environment.getPropertySources();
propertySources1.stream().forEach(x -> {
if (x instanceof MapPropertySource){
MapPropertySource propertySource = (MapPropertySource) x;
if (propertySource.containsProperty(key)){
String proname = propertySource.getName();
Map<String, Object> source = propertySource.getSource();
Map<String, Object> map = new HashMap<>(source.size());
map.putAll(source);
map.put(key, newValue);
environment.getPropertySources().replace(proname, new MapPropertySource(proname, map));
}
}
});
}
@Override
public void setEnvironment(Environment environment) {
RefreshEnvironment.environment = (ConfigurableEnvironment) environment;
}
}
三、找到需要刷新的配置变量
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PropertyPlaceholderHelper;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @className AnoValueRefreshPostProcessor
* @description 找到需要刷新的配置变量
**/
@Component
public class AnoValueRefreshPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements EnvironmentAware {
private Map<String, List<FieldPair>> mapper = new HashMap<>();
private Environment environment;
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
processMetaValue(bean);
return super.postProcessAfterInstantiation(bean, beanName);
}
/**
* description 主要目的就是获取支持动态刷新的配置属性,然后缓存起来
**/
private void processMetaValue(Object bean){
Class<?> aClass = bean.getClass();
if (!aClass.isAnnotationPresent(RefreshValue.class)){
return;
}
try {
for (Field field: aClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Value.class)){
Value val = field.getAnnotation(Value.class);
List<String> keyList = pickPropertyKey(val.value(), 0);
for (String key : keyList) {
mapper.computeIfAbsent(key, (k) -> new ArrayList<>())
.add(new FieldPair(bean, field, val.value()));
}
}
}
} catch (SecurityException e) {
e.printStackTrace();
System.exit(-1);
}
}
/**
* description 实现一个基础的配置文件参数动态刷新支持
**/
private List<String> pickPropertyKey(String value, int begin){
int start = value.indexOf("${", begin) + 2;
if (start < 2){
return new ArrayList<>();
}
int middle = value.indexOf(":", start);
int end = value.indexOf("}", start);
String key;
if (middle > 0 && middle < end){
key = value.substring(start, middle);
} else {
key = value.substring(start, end);
}
List<String> keys = pickPropertyKey(value, end);
keys.add(key);
return keys;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* description 消费事件,接收到变更事件通过key从缓存中找到需要变更的Field,然后依次刷新
**/
@EventListener
public void updateConfig(ConfigUpdateEvent configUpdateEvent){
List<FieldPair> list = mapper.get(configUpdateEvent.key);
if (!CollectionUtils.isEmpty(list)){
list.forEach(f -> f.updateValue(environment));
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class FieldPair{
private static PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}",":", true);
Object bean;
Field field;
String value;
public void updateValue(Environment environment){
boolean accessible = field.isAccessible();
if (!accessible){
field.setAccessible(true);
}
String updateVal = propertyPlaceholderHelper.replacePlaceholders(value, environment::getProperty);
try {
if (field.getType() == String.class){
field.set(bean, updateVal);
}else {
field.set(bean, JSONObject.parseObject(updateVal, field.getType()));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
field.setAccessible(accessible);
}
}
/**
* description 选择事件机制来实现同步,借助spring Event来完成(自定义事件类)
**/
public static class ConfigUpdateEvent extends ApplicationEvent {
String key;
public ConfigUpdateEvent(Object source, String key) {
super(source);
System.out.println("---" + source);
this.key = key;
}
}
}
四、使用接口修改配置
import com.tl.iedb.config.testTwo.AnoValueRefreshPostProcessor;
import com.tl.iedb.config.testTwo.RefreshEnvironment;
import com.tl.iedb.util.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @className TestOne
* @description 修改配置接口
**/
@Slf4j
@RestController
@RequestMapping(value = "/test")
@Api(value = "测试接口")
public class TestOne {
@Autowired
ApplicationContext applicationContext;
@Autowired
ConfigurableEnvironment environment;
@ApiOperation(value = "自定义修改配置")
@PostMapping("/update")
public ResponseResult calcHistoryUseEle(String name, String value){
System.out.println("参数名 name = " + name);
System.out.println("参数 value = " + value);
// 配置转换
RefreshEnvironment.updateValue(name, value);
applicationContext.publishEvent(new AnoValueRefreshPostProcessor.ConfigUpdateEvent(this, name));
return ResponseResult.ok();
}
}
五、 接口验证
import com.tl.iedb.config.testTwo.RefreshValue;
import com.tl.iedb.util.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @className ProxyMetaDefinitionController
* @description 验证@Value值是否修改
**/
@Slf4j
@RestController
@RequestMapping(value = "/tem")
@Api(value = "临时开放接口")
@RefreshValue
public class ProxyMetaDefinitionController {
@Value("${source.flag}")
private boolean enable;
@Value("${db.token}")
private String token;
@Value("${isopen.open}")
private String open;
@ApiOperation(value = "自定义测试")
@PostMapping("/calc/test")
public ResponseResult calcHistoryUseEle(){
System.out.println("---token---" + token);
System.out.println("---enable---" + enable);
System.out.println("---open---" + open);
return ResponseResult.ok();
}
}
以上实现了简单的临时修改@Value值的功能,适合在项目中有一些配置变化,又不想重启项目的时候使用