前言
看到这个文章标题,也许有的看官就觉得很多余,
因为Nacos 可以设置 @NacosValue(value = "${XXX}",autoRefreshed = true) 实现动态刷新;
又因为cloud config的@RefreshScope 实现动态刷新;
还有阿波罗...等
这些玩意的原理其实都很简单,简单说 就是检测到配置文件的修改项后,发布内容变更事件,然后重新刷新绑定值。
那如果我说不准用这些东西呢?
现在就是一个老项目,不给整合这些阿猫阿狗,我想问阁下应该如何应对?
ps: 最近有个朋友在整改旧项目,做了一套小的配置中心系统,在这个配置平台系统上,通过页面能够动态修改刷新配置值。 做完后,这个朋友有些心得,想分享一下。
不多说,开搞。
正文
我们结合示例玩一下。
@Value
@ConfigurationProperties
对应代码:
@Component
public class YouInfos {@Value("${u.infos.name}")private String name;@Value("${u.infos.age}")private Integer age;@Autowiredprivate Environment env;public String getName() {return env.getProperty("u.infos.name");}public Integer getAge() {return Integer.valueOf(Objects.requireNonNull(env.getProperty("u.infos.age")));}public void setName(String name) {this.name = name;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "YouInfos{" +"name='" + name + '\'' +", age=" + age +'}';}
}
@Component
@ConfigurationProperties(prefix = "my.infos")
public class MyInfos {private String name;private Integer age;@Autowiredprivate Environment env;public String getName() { return env.getProperty("my.infos.name"); }public void setName(String name) { this.name = name; }public Integer getAge() { return age; }public void setAge(Integer age) { this.age = age; }@Overridepublic String toString() {return "MyInfos{" +"name='" + name + '\'' +", age=" + age +'}';}
}
对应application.yml的配置(当然也可以是自己额外的配置文件值):
my:infos:name: JCcccage: 18u:infos:name: Doliage: 25
细心的看官,看到这里,已经发现了一些不同。
是的 我把配置项的属性字段的get方法,魔改了一下,写成了重新在 Environment 再拿一次。
简单来说,我希望哪些字段属性是可以达到获取实时数据的,那我就改掉这个字段属性的get方法,让它从头再来过,去 Environment 再拿一次 自己。
接下来,就是我们怎么去改 Environment 里面的key属性值。
结合源码实操玩一下。
① 把环境配置属性拿出来
private static ConfigurableEnvironment environment;
MutablePropertySources propertySources = environment.getPropertySources();
可以看到,这8组配置属性里面, 有一组的名字包含了 application.yml 。
点进去看看是什么:
没错,就是我们的yml 的key 以及value 。
看到这, 大家思路已经比较开明了吧, 我们把这个玩意拿出来, 我们改了哪些key就对应改哪些key(当然新增了哪些,也可以对应去搞)。
看一下这个玩意MutablePropertySources 的源码:
好好好,这么玩是吧,又private 又final 。
这样的情况,我们如何应对?
那当然是最简单的暴力破解 ,反射了:
Field valueFieldOfPropertySources = MutablePropertySources.class.getDeclaredField("propertySourceList");
//设置value属性的访问权限为true
valueFieldOfPropertySources.setAccessible(true);
//获取对象上的value属性的值
List<PropertySource<?>> valueList = (List<PropertySource<?>>) valueFieldOfPropertySources.get(propertySources);
这样我们就把这八组的值拿出来了, 然后就遍历,然后把这个 OriginTrackedMapPropertySource(yml的配置值) 拿出来,改完值,再丢回去, 完事。
代码:
MyRefreshConfigUtil.java
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*** @author JCccc 2023-08-01*/@Component
public class MyRefreshConfigUtil implements EnvironmentAware {private static ConfigurableEnvironment environment;public static void refreshValue(String key, Object newValue) {try {MutablePropertySources propertySources = environment.getPropertySources();Field valueFieldOfPropertySources = MutablePropertySources.class.getDeclaredField("propertySourceList");//设置value属性的访问权限为truevalueFieldOfPropertySources.setAccessible(true);//获取对象上的value属性的值List<PropertySource<?>> valueList = (List<PropertySource<?>>) valueFieldOfPropertySources.get(propertySources);for (PropertySource<?> propertySource :valueList){if (propertySource instanceof OriginTrackedMapPropertySource){Map<String, Object> source = (Map<String, Object>) propertySource.getSource();Map<String, Object> map = new HashMap<>(source.size());map.putAll(source);map.put(key, newValue);environment.getPropertySources().replace(propertySource.getName(), new OriginTrackedMapPropertySource(propertySource.getName(), map));}}} catch (Exception e) {e.printStackTrace();}}@Overridepublic void setEnvironment(Environment environment) {MyRefreshConfigUtil.environment = (ConfigurableEnvironment) environment;}
}
代码简析 :
那么我们再暴露出一个api 接口 ,满足key 和 value的实时刷新(可以写成批量,这里就简单写个单个key意思下):
@GetMapping("/doRefresh")public String doRefresh(@RequestParam String key ,@RequestParam String value) {MyRefreshConfigUtil.refreshValue(key, value);return "refresh success";}
也写个简单的获取配置值接口,看看整体的效果:
@AutowiredMyInfos myInfos;@AutowiredYouInfos youInfos;@GetMapping("/getInfos")public String getInfos() {String myName = myInfos.getName();String yourName = youInfos.getName();return myName+"---"+yourName;}
服务跑起来,先看看我们的配置值:
接下来,我们去修改配置项,然后检测到了配置项哪些key做了改动(这些需要配合前端页面系统化去做比较流畅,就像是一些配置中心页面的保存按钮),然后调用我们的key刷新通知接口:
再调用看下我们的配置值:
好了,就这样吧。