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: 9010
servlet:
context-path: /sm
spring:
application:
name: cloud-sm
cloud:
nacos:
discovery:
server-addr: 192.168.100.101:8848 #Nacos服務註冊中心地址
namespace: 1
config:
server-addr: 192.168.100.101:8848 #Nacos作為配置中心地址
namespace: 1
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(":")[0]);
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, 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();
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(1);
}
}
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" , 1);
return result;
}
}
3)當前nacos的配置文件cloud-sm-constant.properties為空
4)訪問測試路徑localhost:9010/sm/t1,返回為默認值
{
"code": 1,
"string": "test",
"list_int": [
1
],
"list": [
"默認值"
],
"map": {
"KEY": "初始化默認值"
}
}
5)然後更改nacos的配置文件cloud-sm-constant.properties;
6)再次訪問測試路徑localhost:9010/sm/t1,返回為nacos中的值
{
"code": 1,
"string": "12351",
"list_int": [
1,
23,
4
],
"list": [
"123",
"sss"
],
"map": {
"A": 12,
"B": 432
}
}
4.結語
這種實現方式優點如下:
1)動態刷新配置,不需要重啟即可改變程序中的靜態常量值
2)使用簡單,只需在常量類上添加一個註解
3)避免在程序中大量使用@Value,@RefreshScope註解
不足:
此代碼是個人業餘時間的想法,未經過生產驗證,實現的數據類型暫時只寫幾個,其餘的需要自行拓展
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※聚甘新