1.前言

项目中都会使用常量类文件, 这些值如果需要变动需要重新提交代码,或者基于@Value注解实现动态刷新, 如果常量太多也是很麻烦; 那么 能不能有更加简便的实现方式呢?

本文讲述的方式是, 一个JAVA类对应NACOS中的一个配置文件,优先使用nacos中的配置,不配置则使用程序中的默认值;

2.正文

nacos的配置如下图所示,为了满足大多数情况,配置了 namespace命名空间和group;

新建个测试工程 cloud-sm.

bootstrap.yml 中添加nacos相关配置;

为了支持多配置文件需要注意ext-config节点,group对应nacos的添加的配置文件的group; data-id 对应nacos上配置的data-id

配置如下:

server:
port:
servlet:
context-path: /sm
spring:
application:
name: cloud-sm
cloud:
nacos:
discovery:
server-addr: 192.168.100.101: #Nacos服务注册中心地址
namespace:
config:
server-addr: 192.168.100.101: #Nacos作为配置中心地址
namespace:
ext-config:
- group: TEST_GROUP
data-id: cloud-sm.yaml
refresh: true
- group: TEST_GROUP
data-id: cloud-sm-constant.properties
refresh: true

接下来是本文重点:

1)新建注解ConfigModule,用于在配置类上;一个value属性;

2)新建个监听类,用于获取最新配置,并更新常量值

实现流程:

1)项目初始化时获取所有nacos的配置

2)遍历这些配置文件,从nacos上获取配置

3)遍历nacos配置文件,获取MODULE_NAME的值

4)寻找配置文件对应的常量类,从spring容器中寻找 常量类 有注解ConfigModule 且值是 MODULE_NAME对应的

5)使用JAVA反射更改常量类的值

6)增加监听,用于动态刷新

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 ConfigModule {
/**
* 对应配置文件里面key为( MODULE_NAME ) 的值
* @return
*/
String value();
}
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 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 zch
*/
@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;
@Autowired
private ApplicationContext applicationContext;
/**
* 目前只考虑properties 文件
*/
private String fileType = "properties";
/**
* 需要在配置文件中增加一条 MODULE_NAME 的配置,用于找到对应的 常量类
*/
private String MODULE_NAME = "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(":")[]);
if (StringUtils.isNotBlank(namespace)) {
properties.put(PropertyKeyConst.NAMESPACE, namespace);
} ConfigService configService = NacosFactory.createConfigService(properties);
// 处理每个配置文件
for (NacosConfigProperties.Config config : configs.getExtConfig()) {
String dataId = config.getDataId();
String group = config.getGroup();
//目前只考虑properties 文件
if (!dataId.endsWith(fileType)) continue; changeValue(configService.getConfig(dataId, group, )); 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();
try {
proper.load(new StringReader(configInfo)); //把字符串转为reader
} catch (IOException e) {
e.printStackTrace();
}
String moduleName = "";
Enumeration enumeration = proper.propertyNames();
//寻找MODULE_NAME的值
while (enumeration.hasMoreElements()) {
String strKey = (String) enumeration.nextElement();
if (MODULE_NAME.equals(strKey)) {
moduleName = proper.getProperty(strKey);
break;
}
}
if (StringUtils.isBlank(moduleName)) return;
Class curClazz = null;
// 寻找配置文件对应的常量类
// 从spring容器中 寻找类的注解有ConfigModule 且值是 MODULE_NAME对应的
for (String beanName : applicationContext.getBeanDefinitionNames()) {
Class clazz = applicationContext.getBean(beanName).getClass();
ConfigModule configModule = (ConfigModule) clazz.getAnnotation(ConfigModule.class);
if (configModule != null && moduleName.equals(configModule.value())) {
curClazz = clazz;
break;
}
}
if (curClazz == null) return;
// 使用JAVA反射机制 更改常量
enumeration = proper.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String value = proper.getProperty(key);
if (MODULE_NAME.equals(key)) continue;
try {
Field field = curClazz.getDeclaredField(key);
//忽略属性的访问权限
field.setAccessible(true);
Class<?> curFieldType = field.getType();
//其他类型自行拓展
if (curFieldType.equals(String.class)) {
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();
}
}

3.测试

1)新建常量类Constant,增加注解@ConfigModule("sm"),尽量测试全面, 添加常量类型有 String, List,Map

@ConfigModule("sm")
public class Constant { public static volatile String TEST = new String("test"); public static volatile List<String> TEST_LIST = new ArrayList<>();
static {
TEST_LIST.add("默认值");
}
public static volatile Map<String,Object> TEST_MAP = new HashMap<>();
static {
TEST_MAP.put("KEY","初始化默认值");
}
public static volatile List<Integer> TEST_LIST_INT = new ArrayList<>();
static {
TEST_LIST_INT.add();
}
}

2)新建个Controller用于测试这些值

@RestController
public class TestController { @GetMapping("/t1")
public Map<String, Object> test1() {
Map<String, Object> result = new HashMap<>(); result.put("string" , Constant.TEST);
result.put("list" , Constant.TEST_LIST);
result.put("map" , Constant.TEST_MAP);
result.put("list_int" , Constant.TEST_LIST_INT);
result.put("code" , );
return result;
}
}

3)当前nacos的配置文件cloud-sm-constant.properties为空

4)访问测试路径localhost:9010/sm/t1,返回为默认值

{
"code": ,
"string": "test",
"list_int": [ ],
"list": [
"默认值"
],
"map": {
"KEY": "初始化默认值"
}
}

5)然后更改nacos的配置文件cloud-sm-constant.properties;

6)再次访问测试路径localhost:9010/sm/t1,返回为nacos中的值

{
"code": ,
"string": "",
"list_int": [
,
, ],
"list": [
"",
"sss"
],
"map": {
"A": ,
"B":
}
}

4.结语

这种实现方式优点如下:

1)动态刷新配置,不需要重启即可改变程序中的静态常量值

2)使用简单,只需在常量类上添加一个注解

3)避免在程序中大量使用@Value,@RefreshScope注解

不足:

此代码是个人业余时间的想法,未经过生产验证,实现的数据类型暂时只写几个,其余的需要自行拓展

基于NACOS和JAVA反射机制动态更新JAVA静态常量非@Value注解的更多相关文章

  1. Java反射机制动态代理

    1.什么事反射机制动态代理 在一段代码的前后动态执行其他操作,比如有一个方法是往数据库添加一个记录,我们可以通过动态代理,在操作数据库方法的前和后添加代码执行打开数据库连接和关闭数据库连接. 2.演示 ...

  2. java反射机制与动态代理

    在学习HadoopRPC时.用到了函数调用.函数调用都是採用的java的反射机制和动态代理来实现的,所以如今回想下java的反射和动态代理的相关知识. 一.反射 JAVA反射机制定义: JAVA反射机 ...

  3. Java反射机制的学习

    Java反射机制是Java语言被视为准动态语言的关键性质.Java反射机制的核心就是允许在运行时通过Java Reflection APIs来取得已知名字的class类的相关信息,动态地生成此类,并调 ...

  4. Java反射机制(转载)

    原文链接:http://www.blogjava.net/zh-weir/archive/2011/03/26/347063.html Java反射机制是Java语言被视为准动态语言的关键性质.Jav ...

  5. 长篇图解java反射机制及其应用场景

    一.什么是java反射? 在java的面向对象编程过程中,通常我们需要先知道一个Class类,然后new 类名()方式来获取该类的对象.也就是说我们需要在写代码的时候(编译期或者编译期之前)就知道我们 ...

  6. Java反射机制 —— 简单了解

    一.概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为jav ...

  7. 初探Java反射机制

    反射库提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵java代码的程序库.这项功能被大量地应用于JavaBeans中.反射机制提供了在运行状态中获得和调用修改任何一个类的属性和方法的能力. ...

  8. Java基础学习总结(75)——Java反射机制及应用场景

    什么是Java反射机制? JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的以及动态调用对象的方法的功能称为 ...

  9. java反射机制以及应用

    JAVA反射机制+动态运行编译期不存在的JAVA程序 一.有关JAVA反射 在运行期间,在不知道某个类A的内部方法和属性时,能够动态的获取信息.获取类或对象的方法.属性的功能,称之为反射. 1.相关类 ...

随机推荐

  1. 【Elasticsearch学习】DSL搜索大全(持续更新中)

    1.复合查询 复合查询能够组合其他复合查询或者查询子句,同时也可以组合各个查询的查询结果及得分,也可以从Query查询转换为Filter过滤器查询. 首先介绍一下Query Context和 Filt ...

  2. ORCLE 列转行

    字符串转多列 实际上就是拆分字符串的问题,可以使用 substr.instr.regexp_substr函数方式 字符串转多行 使用union all函数等方式 wm_concat函数 wm_conc ...

  3. 【Ubuntu】安装Ubuntu18.04.2LTS

    环境:win10专业版.联想30D9主板 ubuntu:18.04.2LTS:Ubuntu镜像传送门:https://ubuntu.com/download/desktop 有两块硬盘,win10安装 ...

  4. C实现进程间通信(管道; 共享内存,信号量)

    最近学习了操作系统的并发:以下是关于进程间实现并发,通信的两个方法. 例子: 求100000个浮点数的和.要求: (1)随机生成100000个浮点数(父进程). (2)然后创建4个后代进程,分别求25 ...

  5. Parrot os配置源更新

    每次都是忘了怎么配置,去官网查文档,这记一下 一.源文件配置注意 首先要注意Parrot官方软件库的默认更新源文件不在 /etc/apt/sources.list 而是 /etc/apt/source ...

  6. Golang基础教程——map使用篇

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是golang专题的第7篇文章,我们来聊聊golang当中map的用法. map这个数据结构我们经常使用,存储的是key-value的键 ...

  7. ActiveMQ 笔记(六)ActiveMQ的消息存储和持久化

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.持久化机制 1.Activemq持久化 1.1 什么是持久化: 持久化就是高可用的机制,即使服务器宕 ...

  8. Protocol Buffers工作原理

    这里记录一下学习与使用Protocol Buffer的笔记,优点缺点如何使用这里不再叙述,重点关注与理解Protocol Buffers的工作原理,其大概实现. 我们经常使用Protocol Buff ...

  9. (Java实现) 图的m着色问题

    图的m着色问题 [问题描述] 给定无向连通图G和m种不同的颜色.用这些颜色为图G的各顶点着色,每个顶点着一种颜色.如果有一种着色法使G中每条边的2个顶点着不同颜色,则称这个图是m可着色的.图的m着色问 ...

  10. Java实现 LeetCode 71 简化路径

    71. 简化路径 以 Unix 风格给出一个文件的绝对路径,你需要简化它.或者换句话说,将其转换为规范路径. 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身:此外,两个点 (-) 表示将 ...