Springboot interface hard notification realizes ConfigurationProperties and @Value dynamic refresh

foreword


Seeing the title of this article, some readers may feel that it is redundant, 

Because Nacos can set @NacosValue(value = "${XXX}", autoRefreshed = true) to achieve dynamic refresh;

And because @RefreshScope of cloud config realizes dynamic refresh;

And Apollo...etc

The principle of these gadgets is actually very simple. Simply put, after detecting the modification of the configuration file, the content change event is released, and then the binding value is refreshed.

So what if I say no to using these things?

It is an old project now, if you do not integrate these cats and dogs, I would like to ask you how to deal with it?

ps: Recently, a friend was rectifying an old project and built a small configuration center system. On this configuration platform system, the configuration values ​​can be dynamically modified and refreshed through the page. After finishing, this friend has some experience and wants to share.

Don't talk much, let's do it.

text

Let's play around with an example.

@Value

@ConfigurationProperties


 

Corresponding code:
 

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

The configuration corresponding to application.yml (of course, it can also be its own additional configuration file value):

my:
  infos:
    name: JCccc
    age: 18

u:
  infos:
    name: Doli
    age: 25

Careful observers, seeing this, have already discovered some differences.

Yes, I changed the get method of the attribute field of the configuration item, and wrote it to get it again in the Environment.

To put it simply, which field attributes I hope can achieve real-time data acquisition, then I will change the get method of this field attribute, let it start from scratch, and go to Environment to get myself again.

Next, how do we change the value of the key attribute in the Environment.

Play with the source code.

① Take out the environment configuration properties 

  private static ConfigurableEnvironment environment;

  MutablePropertySources propertySources   = environment.getPropertySources();

It can be seen that among the 8 sets of configuration properties, one set of names contains application.yml.

Click to see what it is:

That's right, it is the key and value of our yml.

Seeing this, everyone’s thinking is relatively open, let’s take this thing out, and we will change which keys we change accordingly (of course, we can also do corresponding ones for new ones).

Take a look at the source code of this MutablePropertySources:

Okay, let’s play like this, it’s private and final.

How do we deal with this situation?

That's of course the easiest brute force, reflecting: 

            Field valueFieldOfPropertySources = MutablePropertySources.class.getDeclaredField("propertySourceList");
            //Set the access permission of the value property to true
            valueFieldOfPropertySources.setAccessible(true);
            //Get the value of the value property on the object
            List<PropertySource<?>> valueList = (List<PropertySource<?>>) valueFieldOfPropertySources. get(propertySources);
 

In this way, we took out the values ​​of these eight groups, then traversed, and then took out the OriginTrackedMapPropertySource (yml configuration value), changed the value, and then threw it back, and it was done.

Code:

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

Code analysis:


Then we expose an api interface to meet the real-time refresh of key and value (it can be written in batches, here is a simple description of a single key):
 

    @GetMapping("/doRefresh")
    public String doRefresh(@RequestParam String key ,@RequestParam String value) {
        MyRefreshConfigUtil.refreshValue(key, value);
        return  "refresh success";
    }

Also write a simple interface to get the configuration value to see the overall effect:
 

    @Autowired
    MyInfos myInfos;
    @Autowired
    YouInfos youInfos;

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

The service is running, first look at our configuration values:

Next, we modify the configuration items, and then detect which keys of the configuration items have been changed (these need to be coordinated with the front-end page system to make it smoother, just like the save button of some configuration center pages), and then call our key refresh Notification interface:

 

Then call to see our configuration value:

Well, that's it. 

Guess you like

Origin blog.csdn.net/qq_35387940/article/details/132605856