吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2095|回复: 24
收起左侧

[Java 原创] java动态刷新@Value

  [复制链接]
jiaowojiangge 发表于 2024-1-28 11:20

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

免费评分

参与人数 7吾爱币 +13 热心值 +7 收起 理由
liuyong728 + 1 + 1 谢谢@Thanks!
1124480274 + 1 + 1 用心讨论,共获提升!
带俗 + 1 + 1 热心回复!
soenluzy + 1 + 1 谢谢@Thanks!
wushaominkk + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Naive2021 + 1 + 1 我很赞同!
zyh666 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

199556 发表于 2024-1-29 10:43
invadeo 发表于 2024-1-29 08:29
RefreshValue注解写错了吧

平台识别错误,他复制进来的时候是正确的,但是平台给改啦,需要把
NineSu 发表于 2024-11-21 16:57
好方法!
但是有个场景如何更新。
比如线程池配置,提供配置文件读取配置线程池大小,队列大小,拒绝策略等信息。然后启动的时候根据配置创建线程池。
这种情况下,如果动态更新了配置,已经创建的线程池不会修改对应的属性的吧。这种问题有方案吗。
urdarling 发表于 2024-1-28 16:53
yuxuan1311 发表于 2024-1-28 18:34
还能这样操作啊
reinmj 发表于 2024-1-28 19:25
这个在一些课程看过,挺实用的
TheKingOfKiller 发表于 2024-1-28 20:31
很棒,支持,有学到了
abc814360602 发表于 2024-1-28 22:06
写个@Aspect切面
头像被屏蔽
moruye 发表于 2024-1-28 22:08
提示: 作者被禁止或删除 内容自动屏蔽
turmasi1234 发表于 2024-1-29 07:35
感谢分享,这个是用起来很方便
invadeo 发表于 2024-1-29 08:29
RefreshValue注解写错了吧
微信截图_20240129082815.png
TearApart 发表于 2024-1-29 08:55

大佬用切面可以怎么实现
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-21 22:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表