Home  >  Article  >  Java  >  How to dynamically update yml files in SpringBoot

How to dynamically update yml files in SpringBoot

WBOY
WBOYforward
2023-05-12 16:22:061136browse

Project dependencies

The project is based on version 2.0.0.RELEASE, so snakeyaml needs to be introduced separately, and higher versions are included

        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.23</version>
        </dependency>

Most methods on the Internet are to introduce spring-cloud The -context configuration component calls the refresh method of ContextRefresher to achieve the same effect. Consider the following two points that are not used.

  • The development framework uses logback logs. The introduction of spring-cloud-context will cause log configuration reading Get the error

  • Introducing spring-cloud-context will also introduce the spring-boot-starter-actuator component, which will open some health check routes and ports, requiring additional control over framework security

YML file content acquisition

Reading files under the resource file requires using ClassPathResource to obtain the InputStream

    public String getTotalYamlFileContent() throws Exception {
        String fileName = "application.yml";
        return getYamlFileContent(fileName);
    }
    public String getYamlFileContent(String fileName) throws Exception {
        ClassPathResource classPathResource = new ClassPathResource(fileName);
        return onvertStreamToString(classPathResource.getInputStream());
    }
    public static String convertStreamToString(InputStream inputStream) throws Exception{
       return IOUtils.toString(inputStream, "utf-8");
    }

YML file content update

After we obtain the content of the yml file, we visually display it to the front desk for display modification, convert the modified content into a Map structure through the yaml.load method, and then use yaml.dumpAsMap to convert it into a stream and write it to the file

    public void updateTotalYamlFileContent(String content) throws Exception {
        String fileName = "application.yml";
        updateYamlFileContent(fileName, content);
    }
	public void updateYamlFileContent(String fileName, String content) throws Exception {
        Yaml template = new Yaml();
        Map<String, Object> yamlMap = template.load(content);
        ClassPathResource classPathResource = new ClassPathResource(fileName);
        Yaml yaml = new Yaml();
        //字符输出
        FileWriter fileWriter = new FileWriter(classPathResource.getFile());
        //用yaml方法把map结构格式化为yaml文件结构
        fileWriter.write(yaml.dumpAsMap(yamlMap));
        //刷新
        fileWriter.flush();
        //关闭流
        fileWriter.close();
    }

YML Property refresh

There are generally three ways to read and use yml properties in the program

Use Value annotation

    @Value("${system.systemName}")
    private String systemName;

Read through environment injection

    @Autowired
    private Environment environment;
    environment.getProperty("system.systemName")

Use ConfigurationProperties annotation Read

@Component
@ConfigurationProperties(prefix = "system")
public class SystemConfig {
    private String systemName;
}

Property refresh

The configuration collection we read through the environment.getProperty method is actually stored in PropertySources. We only need to take out all the key-value pairs and store them in the propertyMap. Convert the content of the updated yml file into a ymlMap of the same format, merge the two Maps, and call the replace method of PropertySources for overall replacement.

But both the ymlMap after yaml.load and the propertyMap taken out by PropertySources Data deconstruction is different and requires manual conversion

The propertyMap collection is a simple key and value pair, and the key is a name in the form of properties, such as system.systemName=>xxxxx Group Management System

ymlMap collection is the key, the nested hierarchical structure of LinkedHashMap, for example system=>(systemName=>xxxxx Group Management System)

The conversion method is as follows

  public HashMap<String, Object> convertYmlMapToPropertyMap(Map<String, Object> yamlMap) {
        HashMap<String, Object> propertyMap = new HashMap<String, Object>();
        for (String key : yamlMap.keySet()) {
            String keyName = key;
            Object value = yamlMap.get(key);
            if (value != null && value.getClass() == LinkedHashMap.class) {
                convertYmlMapToPropertyMapSub(keyName, ((LinkedHashMap<String, Object>) value), propertyMap);
            } else {
                propertyMap.put(keyName, value);
            }
        }
        return propertyMap;
    }
    private void convertYmlMapToPropertyMapSub(String keyName, LinkedHashMap<String, Object> submMap, Map<String, Object> propertyMap) {
        for (String key : submMap.keySet()) {
            String newKey = keyName + "." + key;
            Object value = submMap.get(key);
            if (value != null && value.getClass() == LinkedHashMap.class) {
                convertYmlMapToPropertyMapSub(newKey, ((LinkedHashMap<String, Object>) value), propertyMap);
            } else {
                propertyMap.put(newKey, value);
            }
        }
    }

The refresh method is as follows

        String name = "applicationConfig: [classpath:/" + fileName + "]";
        MapPropertySource propertySource = (MapPropertySource) environment.getPropertySources().get(name);
        Map<String, Object> source = propertySource.getSource();
        Map<String, Object> map = new HashMap<>(source.size());
        map.putAll(source);
        Map<String, Object> propertyMap = convertYmlMapToPropertyMap(yamlMap);
        for (String key : propertyMap.keySet()) {
            Object value = propertyMap.get(key);
            map.put(key, value);
        }
        environment.getPropertySources().replace(name, new MapPropertySource(name, map));

Annotation Refresh

Whether it is the Value annotation or the ConfigurationProperties annotation, it is actually used by injecting the property method of the Bean object. We first customize the annotation RefreshValue to modify the class of the Bean where the property is located

By implementing the InstantiationAwareBeanPostProcessorAdapter interface, the corresponding beans are filtered and stored when the system starts. When updating the yml file, the properties of the corresponding

bean can be updated through spring's event notification.

Register events Use the EventListener annotation

    @EventListener
    public void updateConfig(ConfigUpdateEvent configUpdateEvent) {
        if(mapper.containsKey(configUpdateEvent.key)){
            List<FieldPair> fieldPairList = mapper.get(configUpdateEvent.key);
            if(fieldPairList.size()>0){
                for (FieldPair fieldPair:fieldPairList) {
                    fieldPair.updateValue(environment);
                }
            }
        }
    }

Notify the trigger event and use the publishEvent method of ApplicationContext

    @Autowired
    private ApplicationContext applicationContext;
  	for (String key : propertyMap.keySet()) {
       applicationContext.publishEvent(new YamlConfigRefreshPostProcessor.ConfigUpdateEvent(this, key));
    }

The complete code of YamlConfigRefreshPostProcessor is as follows

@Component
public class YamlConfigRefreshPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements EnvironmentAware {
    private Map> mapper = new HashMap<>();
    private Environment environment;
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        processMetaValue(bean);
        return super.postProcessAfterInstantiation(bean, beanName);
    }
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
    private void processMetaValue(Object bean) {
        Class clz = bean.getClass();
        if (!clz.isAnnotationPresent(RefreshValue.class)) {
            return;
        }
        if (clz.isAnnotationPresent(ConfigurationProperties.class)) {
            //@ConfigurationProperties注解
            ConfigurationProperties config = (ConfigurationProperties) clz.getAnnotation(ConfigurationProperties.class);
            for (Field field : clz.getDeclaredFields()) {
                String key = config.prefix() + "." + field.getName();
                if(mapper.containsKey(key)){
                    mapper.get(key).add(new FieldPair(bean, field, key));
                }else{
                    List fieldPairList = new ArrayList<>();
                    fieldPairList.add(new FieldPair(bean, field, key));
                    mapper.put(key, fieldPairList);
                }
            }
        } else {
            //@Valuez注解
            try {
                for (Field field : clz.getDeclaredFields()) {
                    if (field.isAnnotationPresent(Value.class)) {
                        Value val = field.getAnnotation(Value.class);
                        String key = val.value().replace("${", "").replace("}", "");
                        if(mapper.containsKey(key)){
                            mapper.get(key).add(new FieldPair(bean, field, key));
                        }else{
                            List fieldPairList = new ArrayList<>();
                            fieldPairList.add(new FieldPair(bean, field, key));
                            mapper.put(key, fieldPairList);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(-1);
            }
        }
    }
    public static class FieldPair {
        private static PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}",
                ":", true);
        private Object bean;
        private Field field;
        private String value;
        public FieldPair(Object bean, Field field, String value) {
            this.bean = bean;
            this.field = field;
            this.value = value;
        }
        public void updateValue(Environment environment) {
            boolean access = field.isAccessible();
            if (!access) {
                field.setAccessible(true);
            }
            try {
                if (field.getType() == String.class) {
                    String updateVal = environment.getProperty(value);
                    field.set(bean, updateVal);
                }
                else if (field.getType() == Integer.class) {
                    Integer updateVal = environment.getProperty(value,Integer.class);
                    field.set(bean, updateVal);
                }
                else if (field.getType() == int.class) {
                    int updateVal = environment.getProperty(value,int.class);
                    field.set(bean, updateVal);
                }
                else if (field.getType() == Boolean.class) {
                    Boolean updateVal = environment.getProperty(value,Boolean.class);
                    field.set(bean, updateVal);
                }
                else if (field.getType() == boolean.class) {
                    boolean updateVal = environment.getProperty(value,boolean.class);
                    field.set(bean, updateVal);
                }
                else {
                    String updateVal = environment.getProperty(value);
                    field.set(bean, JSONObject.parseObject(updateVal, field.getType()));
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            field.setAccessible(access);
        }
        public Object getBean() {
            return bean;
        }
        public void setBean(Object bean) {
            this.bean = bean;
        }
        public Field getField() {
            return field;
        }
        public void setField(Field field) {
            this.field = field;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }
    public static class ConfigUpdateEvent extends ApplicationEvent {
        String key;
        public ConfigUpdateEvent(Object source, String key) {
            super(source);
            this.key = key;
        }
    }
    @EventListener
    public void updateConfig(ConfigUpdateEvent configUpdateEvent) {
        if(mapper.containsKey(configUpdateEvent.key)){
            List<FieldPair> fieldPairList = mapper.get(configUpdateEvent.key);
            if(fieldPairList.size()>0){
                for (FieldPair fieldPair:fieldPairList) {
                    fieldPair.updateValue(environment);
                }
            }
        }
    }
}

The above is the detailed content of How to dynamically update yml files in SpringBoot. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete