首页 > 其他分享 >Nacos热更新静态变量配置

Nacos热更新静态变量配置

时间:2024-05-16 13:18:57浏览次数:19  
标签:Map return String 静态 Nacos value key import 变量

Nacos热更新静态变量配置

Springboot项目接入nacos,配置文件统一管理,但静态常量无法通过@Value注解实时热更新(如下所示)。

GlobalVariables.java

@Component
public class GlobalVariables {
  
    //测试热加载配置字段
    public static String testInfo;
  
    @Value("${testInfo}")
    public void setTestInfo(String value) {
        testInfo = value;
    }
}

解决思路:

  1. 项目初始化时获取所有nacos的配置
  2. 遍历这些配置文件,从nacos上获取配置
  3. 寻找配置文件对应的常量类,从spring容器中寻找 常量类 有注解NacosConfig
  4. 使用JAVA反射更改常量类的值
  5. 增加监听,用于动态刷新

1、bootstrap.yml 配置

spring:
  application:
    name: test
  cloud:
    nacos:
      config:
        # nacos的ip地址和端口
        server-addr: 127.0.0.1:8848
        # nacos登录用户名
        username: nacos
        # nacos登录密码
        password: nacos
        # nacos命名空间id为 dev
        namespace: 07e01034-cba5-45b2-88cf-e14d3bf1fa60
        # 创建的配置的group
        group: DEFAULT_GROUP
        # 配置文件的后缀名
        file-extension: yaml
        prefix: ${spring.application.name}

2、nacos 配置

image-20240516111024406

配置增加中加入参数 testInfo

image-20240516111100712

3、增加注解

NacosConfig.java

import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Component
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface NacosConfig {

}

GlobalVariables.java

@NacosConfig
@Component
public class GlobalVariables {
  
    //测试热加载配置字段
    public static String testInfo;
  
    @Value("${testInfo}")
    public void setTestInfo(String value) {
        testInfo = value;
    }
}

4、增加监听事件

**NacosConfigListener.java **

import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.utils.LogUtils;
import com.gisquest.common.core.util.PropertiesUtil;
import com.gisquest.xmgzt.config.NacosConfig;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * nacos 自定义监听
 *
 * @author wwj
 */
@Component
public class NacosConfigListener {
    private Logger LOGGER = LogUtils.logger(NacosConfigListener.class);
    @Autowired
    private NacosConfigProperties configs;
    @Value("${spring.cloud.nacos.config.server-addr:}")
    private String serverAddr;
    @Value("${spring.cloud.nacos.config.namespace:}")
    private String namespace;
    @Value("${spring.cloud.nacos.config.username:}")
    private String username;
    @Value("${spring.cloud.nacos.config.password:}")
    private String password;
    @Autowired
    private ApplicationContext applicationContext;
    /**
     * 目前只考虑yaml 文件
     */
    private String fileType = "yaml";
    /**
     * 需要在配置文件中增加一条 MODULE_NAME 的配置,用于找到对应的 常量类
     */

    /**
     * NACOS监听方法
     *
     * @throws NacosException
     */
    public void listener() throws NacosException {
        if (StringUtils.isBlank(serverAddr)) {
            LOGGER.info("未找到 spring.cloud.nacos.config.server-addr");
            return;
        }
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr.split(":")[0]);
        if (StringUtils.isNotBlank(namespace)) {
            properties.put(PropertyKeyConst.NAMESPACE, namespace);
        }
        properties.put(PropertyKeyConst.USERNAME, username);
        properties.put(PropertyKeyConst.PASSWORD, password);

        ConfigService configService = NacosFactory.createConfigService(properties);
        List<NacosConfigProperties.Config> sharedConfigs = configs.getSharedConfigs();
        // 处理每个配置文件
        for (NacosConfigProperties.Config config : sharedConfigs) {
            String dataId = config.getDataId();
            String group = config.getGroup();
            //目前只考虑yaml 文件
            if (!dataId.endsWith(fileType)) continue;
            changeValue(configService.getConfig(dataId, group, 5000));
            configService.addListener(dataId, group, new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    changeValue(configInfo);
                }

                @Override
                public Executor getExecutor() {
                    return null;
                }
            });
        }
    }

    /**
     * 改变 常量类的 值
     *
     * @param configInfo
     */
    private void changeValue(String configInfo) {
        if(StringUtils.isBlank(configInfo)) return;
        Properties proper = new Properties();
        //yaml转Properties
        String s = PropertiesUtil.castToProperties(configInfo);
        try {
            proper.load(new StringReader(s)); //把字符串转为reader
        } catch (IOException e) {
            e.printStackTrace();
        }
        Enumeration enumeration = proper.propertyNames();

        // 寻找配置文件对应的常量类
        //从spring容器中 寻找类的注解有NacosConfig
        for (String beanName : applicationContext.getBeanDefinitionNames()) {
            Class curClazz = applicationContext.getBean(beanName).getClass();
            NacosConfig configModule = (NacosConfig) curClazz.getAnnotation(NacosConfig.class);
            if (configModule != null) {
                // 使用JAVA反射机制 更改常量
                while (enumeration.hasMoreElements()) {
                    String key = (String) enumeration.nextElement();
                    String value = proper.getProperty(key);
                    try {
                        Field field = curClazz.getDeclaredField(key);
                        System.out.println(field);
                        //忽略属性的访问权限
                        field.setAccessible(true);
                        Class<?> curFieldType = field.getType();
                        //其他类型自行拓展
                        if (curFieldType.equals(String.class)) {
                            field.set(null, value);
                        } else if (curFieldType.equals(Integer.class)) { // Integer元素
                            field.set(null, value);
                        } else if (curFieldType.equals(Boolean.class)) { // Boolean元素
                            field.set(null, value);
                        }else if (curFieldType.equals(List.class)) { // 集合List元素
                            field.set(null, JSONUtils.parse(value));
                        } else if (curFieldType.equals(Map.class)) { //Map
                            field.set(null, JSONUtils.parse(value));
                        }
                    } catch (NoSuchFieldException | IllegalAccessException e) {
                        LOGGER.info("设置属性失败:{} {} = {} ", curClazz.toString(), key, value);
                    }
                }
            }
        }

    }

    @PostConstruct
    public void init() throws NacosException {
        listener();
    }
}

PropertiesUtil.java (Properties工具类)

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.util.CollectionUtils;
import org.yaml.snakeyaml.Yaml;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Deng.Weiping
 * @since 2023/11/28 13:57
 */
public class PropertiesUtil {

    /**
     * yaml 转 Properties
     *
     * @param input
     * @return
     */
    public static String castToProperties(String input) {
        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        Map<String, Object> yamlMap = new Yaml().load(input);
        flattenMap("", yamlMap, propertiesMap);
        StringBuffer strBuff = new StringBuffer();
        propertiesMap.forEach((key, value) -> strBuff.append(key)
                .append("=")
                .append(value)
                .append(StrUtil.LF));
        return strBuff.toString();
    }

    /**
     * Properties 转 Yaml
     *
     * @param input
     * @return
     */
    public static String castToYaml(String input) {
        try {
            Map<String, Object> properties = readProperties(input);
            return properties2Yaml(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static Map<String, Object> readProperties(String input) {
        // 使用 LinkedHashMap 保证顺序
        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        for (String line : input.split(StrUtil.LF)) {
            if (StrUtil.isNotBlank(line)) {
                // 使用正则表达式解析每一行中的键值对
                Pattern pattern = Pattern.compile("\\s*([^=\\s]*)\\s*=\\s*(.*)\\s*");
                Matcher matcher = pattern.matcher(line);
                if (matcher.matches()) {
                    String key = matcher.group(1);
                    String value = matcher.group(2);
                    propertiesMap.put(key, value);
                }
            }
        }
        return propertiesMap;
    }

    /**
     * 递归 Map 集合,转为 Properties集合
     *
     * @param prefix
     * @param yamlMap
     * @param treeMap
     */
    private static void flattenMap(String prefix, Map<String, Object> yamlMap, Map<String, Object> treeMap) {
        yamlMap.forEach((key, value) -> {
            String fullKey = prefix + key;
            if (value instanceof LinkedHashMap) {
                flattenMap(fullKey + ".", (LinkedHashMap) value, treeMap);
            } else if (value instanceof ArrayList) {
                List values = (ArrayList) value;
                for (int i = 0; i < values.size(); i++) {
                    String itemKey = String.format("%s[%d]", fullKey, i);
                    Object itemValue = values.get(i);
                    if (itemValue instanceof String) {
                        treeMap.put(itemKey, itemValue);
                    } else {
                        flattenMap(itemKey + ".", (LinkedHashMap) itemValue, treeMap);
                    }
                }
            } else {
                treeMap.put(fullKey, null != value ? value.toString() : null);
            }
        });
    }

    /**
     * properties 格式转化为 yaml 格式字符串
     *
     * @param properties
     * @return
     */
    private static String properties2Yaml(Map<String, Object> properties) {
        if (CollUtil.isEmpty(properties)) {
            return null;
        }
        Map<String, Object> map = parseToMap(properties);
        StringBuffer stringBuffer = map2Yaml(map);
        return stringBuffer.toString();
    }

    /**
     * 递归解析为 LinkedHashMap
     *
     * @param propMap
     * @return
     */
    private static Map<String, Object> parseToMap(Map<String, Object> propMap) {
        Map<String, Object> resultMap = new LinkedHashMap<>();
        try {
            if (CollectionUtils.isEmpty(propMap)) {
                return resultMap;
            }
            propMap.forEach((key, value) -> {
                if (key.contains(".")) {
                    String currentKey = key.substring(0, key.indexOf("."));
                    if (resultMap.get(currentKey) != null) {
                        return;
                    }
                    Map<String, Object> childMap = getChildMap(propMap, currentKey);
                    Map<String, Object> map = parseToMap(childMap);
                    resultMap.put(currentKey, map);
                } else {
                    resultMap.put(key, value);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultMap;
    }


    /**
     * 获取拥有相同父级节点的子节点
     *
     * @param propMap
     * @param currentKey
     * @return
     */
    private static Map<String, Object> getChildMap(Map<String, Object> propMap, String currentKey) {
        Map<String, Object> childMap = new LinkedHashMap<>();
        try {
            propMap.forEach((key, value) -> {
                if (key.contains(currentKey + ".")) {
                    key = key.substring(key.indexOf(".") + 1);
                    childMap.put(key, value);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return childMap;
    }

    /**
     * map集合转化为yaml格式字符串
     *
     * @param map
     * @return
     */
    public static StringBuffer map2Yaml(Map<String, Object> map) {
        //默认deep 为零,表示不空格,deep 每加一层,缩进两个空格
        return map2Yaml(map, 0);
    }

    /**
     * 把Map集合转化为yaml格式 String字符串
     *
     * @param propMap map格式配置文件
     * @param deep    树的层级,默认deep 为零,表示不空格,deep 每加一层,缩进两个空格
     * @return
     */
    private static StringBuffer map2Yaml(Map<String, Object> propMap, int deep) {
        StringBuffer yamlBuffer = new StringBuffer();
        try {
            if (CollectionUtils.isEmpty(propMap)) {
                return yamlBuffer;
            }
            String space = getSpace(deep);
            for (Map.Entry<String, Object> entry : propMap.entrySet()) {
                Object valObj = entry.getValue();
                if (entry.getKey().contains("[") && entry.getKey().contains("]")) {
                    String key = entry.getKey().substring(0, entry.getKey().indexOf("[")) + ":";
                    yamlBuffer.append(space + key + "\n");
                    propMap.forEach((itemKey, itemValue) -> {
                        if (itemKey.startsWith(key.substring(0, entry.getKey().indexOf("[")))) {
                            yamlBuffer.append(getSpace(deep + 1) + "- ");
                            if (itemValue instanceof Map) {
                                StringBuffer valStr = map2Yaml((Map<String, Object>) itemValue, 0);
                                String[] split = valStr.toString().split(StrUtil.LF);
                                for (int i = 0; i < split.length; i++) {
                                    if (i > 0) {
                                        yamlBuffer.append(getSpace(deep + 2));
                                    }
                                    yamlBuffer.append(split[i]).append(StrUtil.LF);
                                }
                            } else {
                                yamlBuffer.append(itemValue + "\n");
                            }
                        }
                    });
                    break;
                } else {
                    String key = space + entry.getKey() + ":";
                    if (valObj instanceof String) { //值为value 类型,不用再继续遍历
                        yamlBuffer.append(key + " " + valObj + "\n");
                    } else if (valObj instanceof List) { //yaml List 集合格式
                        yamlBuffer.append(key + "\n");
                        List<String> list = (List<String>) entry.getValue();
                        String lSpace = getSpace(deep + 1);
                        for (String str : list) {
                            yamlBuffer.append(lSpace + "- " + str + "\n");
                        }
                    } else if (valObj instanceof Map) { //继续递归遍历
                        Map<String, Object> valMap = (Map<String, Object>) valObj;
                        yamlBuffer.append(key + "\n");
                        StringBuffer valStr = map2Yaml(valMap, deep + 1);
                        yamlBuffer.append(valStr.toString());
                    } else {
                        yamlBuffer.append(key + " " + valObj + "\n");
                    }
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return yamlBuffer;
    }

    /**
     * 获取缩进空格
     *
     * @param deep
     * @return
     */
    private static String getSpace(int deep) {
        StringBuffer buffer = new StringBuffer();
        if (deep == 0) {
            return "";
        }
        for (int i = 0; i < deep; i++) {
            buffer.append("  ");
        }
        return buffer.toString();
    }
}

5、总结

这种实现方式优点如下:

  1. 动态刷新配置,不需要重启即可改变程序中的静态常量值
  2. 使用简单,只需在常量类上添加一个注解
  3. 避免在程序中大量使用@Value,@RefreshScope注解

标签:Map,return,String,静态,Nacos,value,key,import,变量
From: https://www.cnblogs.com/995i996/p/18195802

相关文章

  • MacOS环境变量source生效但重启后又失效
      .bash_profile和.zshrc都是macos系统重环境变量配置的文件,但是两者有不同之处。.bash_profile:在执行source~/.bash_profile,只在当前窗口生效,但关闭当前终端窗口或者mac关机重启后不会再生效。.zshrc:在执行source~/.zshrc,这是永久生效的,mac每次启动会自动执行source......
  • spring bot 中处理静态资源
    当需要引入前端资源时,有许多静态资源,比如:css,js等文件第一种方式:webjarsWebjars本质就是以jar包的方式引入静态资源,以前要导入一个静态资源文件,直接导入即可,但使用springboot需要使用webjars,比如使用jquery时,只需要引入jquery对应版本的pom依赖即可;<dependency><grou......
  • TS变量类型
    // TS的变量类型// 1.数字类型leta:number=1;// 2.字符串类型letb:string="hello";// 3.布尔类型letc:boolean=true;// 4.any类型(赋值给其他类型的时候会改变原类型,所以不常用)letd:any="jjjj";d=2;d=false;// 5.unKnown类型(类型......
  • 运维-微服务组件nacos(未写完)
    一、基础概念1.Nacos的概念Nacos/nɑ:kəʊs/是DynamicNamingandConfigurationService的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos致力于帮助您发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助您快速实现动态......
  • javascript 将变量值作为对象属性 获取对象对应的值
      test(){letform={bar_rule_txt:'{spu}-{master_attr_value}-{slave_attr_alias}',bar_rule_result:'',spu:'JPK1575G',master_attr_value:'黑色',master......
  • Nacos2.2.0适配瀚高数据库,打镜像部署
    一、Nacos2.2.0适配瀚高数据库Nacos2.2.0适配瀚高数据库部分参考地址:https://blog.csdn.net/weixin_39676699/article/details/130642890application.properties配置文件中数据库部分配置如下:spring.sql.init.platform=highgodb.num=1db.url.0=jdbc:highgo://192.168.1.1:58......
  • win32下vs2013汇编传参和局部变量栈的分配
    1、传参无论是用寄存器还是栈,最终都要压入栈中。2、第一个参数的位置是ebp+8,少于4byte的数据类型按照4Byte压栈,第n个参数的地址是[ebp+4+4n],ebp+4是返回地址。8byte的数据类型,会先开辟8字节的栈空间esp-8,再把数据放入栈中。下一个参数的地址相应的要加8。3、局部变量用函数自己......
  • shell编程规范与变量
    shell脚本基础1.shell概述shell脚本的概念将要执行的命令按顺序保存到一个文本文件给该文件可执行权限可结合各种shell控制语句以完成更复杂的操作shell应用应用场景重复性操作交互性操作批量事务处理服务运行状态监控定时任务执行应用场景shell的作用Linux系统......
  • Python如何访问闭包中的变量
    你想要扩展函数中的某个闭包,允许它能访问和修改函数的内部变量。解决方案通常,闭包的内部变量对外界是完全隐藏的。但可以编写访问函数,将其作为函数属性绑定到闭包上来实现访问。defsample():n=0#闭包函数deffunc():print('n=',n)#属性n的......
  • shell重定向与变量
    一、重定向与管道符重定向重定向:改变电脑的数据输出方向,默认是输出在屏幕上类型设备文件文件描述编号默认设备标准输入/dev/stdin0键盘标准输出/dev/stdout1显示器标准错误输出/dev/stderr2显示器交互式硬件设备标准输入:从该设备接收用户输入的数......