java动态刷新@Value

前言:在工作中需要实现动态刷新@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 home.php?mod=space&uid=593715 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 source = propertySource.getSource();
                    Map 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> 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 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 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 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 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值的功能,适合在项目中有一些配置变化,又不想重启项目的时候使用

你可能感兴趣的:(java,java,开发语言)