Springboot 接口方式硬通知实现ConfigurationProperties 、@Value 动态刷新

前言


看到这个文章标题,也许有的看官就觉得很多余, 

因为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;
    @Autowired
    private 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;
    }
    @Override
    public String toString() {
        return "YouInfos{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
@Component
@ConfigurationProperties(prefix = "my.infos")
public class MyInfos {
    private String name;
    private Integer age;
    @Autowired
    private 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; }

    @Override
    public String toString() {
        return "MyInfos{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

对应application.yml的配置(当然也可以是自己额外的配置文件值):

my:
  infos:
    name: JCccc
    age: 18

u:
  infos:
    name: Doli
    age: 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属性的访问权限为true
            valueFieldOfPropertySources.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();
        }
    }

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

也写个简单的获取配置值接口,看看整体的效果:
 

    @Autowired
    MyInfos myInfos;
    @Autowired
    YouInfos youInfos;

    @GetMapping("/getInfos")
    public String getInfos() {
        String myName = myInfos.getName();
        String yourName = youInfos.getName();
        return myName+"---"+yourName;
    }

服务跑起来,先看看我们的配置值:

接下来,我们去修改配置项,然后检测到了配置项哪些key做了改动(这些需要配合前端页面系统化去做比较流畅,就像是一些配置中心页面的保存按钮),然后调用我们的key刷新通知接口:

 

再调用看下我们的配置值:

好了,就这样吧。 

猜你喜欢

转载自blog.csdn.net/qq_35387940/article/details/132605856