合并Map集合
合并Map集合有很多方法,例如Map自生提供的putAll()
方法。但是这种方法只能合并一层的集合,如果是多层的呢?
场景
现在有一个yml配置文件,由于项目部署在多台服务器上,而且每台服务器上的配置有些许差异。每次系统升级的时候都需要手动去配置。
这次想着写个程序给我们手动配置。
方案
需要一个源配置文件application.yml
,每台服务器都有自己的配置文件。
需要开发人员给一个changeApplication.yml
,这个配置文件里需要按一定的规则写好配置。
开发一个程序,打个jar包,运行jar包后把changeApplication.yml
中的数据和合并到application.yml
实现
读取yml程序的工具类Yml需要引入依赖
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>
读取yml文件,并转换成Map集合
/**
* 将yaml配置文件转化成map
* @param fileName
* @return
*/
public Map<String, Object> getYamlToMap(String fileName) {
LinkedHashMap<String, Object> yamls;
Yaml yaml = new Yaml();
try {
@Cleanup InputStream in = new FileInputStream(fileName);
yamls = yaml.loadAs(in, LinkedHashMap.class);
} catch (Exception e) {
System.out.println(fileName + " 读取失败:" + e);
return null;
}
return yamls;
}
递归实现Map合并核心代码
/**
* 将fromMap的数据合并到toMap中
* @param toMap 配置文件的map
* @param fromMap 需要修改的配置,只能是Map
*/
public void mergeMap(Map<String, Object> toMap, Map<String, Object> fromMap) {
Set<String> keys = fromMap.keySet();
for (String key : keys) {
Object toValue = toMap.get(key);
Object fromValue = fromMap.get(key);
if (!toMap.containsKey(key) || !(toValue instanceof Map && fromValue instanceof Map)) {
// 源配置中没有这个key,则直接将这个key对应的配置赋给源配置即可。
// toValue是Map,fromValue不是Map,则表示要将源配置中的配置改成简单类型,不再有多层级了
// fromValue是Map,toValue不是Map,则表示源配置只是一个简单的类型,现在要改成多层级的配置。
toMap.put(key, fromValue);
} else {
// 源配置中有这个key,且fromValue是一个单一的值,则直接替换即可(此时toValue可能是一个)
mergeMap((Map<String, Object>) toValue, (Map<String, Object>) fromValue);
}
}
}
现在能够实现Map合并了,但是咱们的需求是对于不同的环境要配置不同的东西。
首先我的application.yml
里面有个配置provinceCode
,这个配置是每台服务器上的配置文件都有的,我们也会用这个配置来区分是哪台服务器。
现在我有30台服务器,需要考虑配置时的三种场景:
-
某些配置我是想所有服务器都要配置
-
某些配置我是希望指定的几台服务器配置,其他的不配置
-
某些配置我是希望指定的几台服务器不配置,除此之外的都需要配置
基于上述三种场景,我们需要在changeApplication.yml
中按不同的规则配置。
excludeConfigKey:
"320000":
bj: 省份320000这个配置不会生效
"540000,520000":
notHas: 除了540000,520000之外的省份这个配置会生效
includeConfigKey:
"310000,540000":
name: window
isAgf: true
hello: 2342
allConfigKey:
huawei:
version: 2.3.4
name: 华为
type: mini
spring:
resources:
chain:
strategy:
content:
text: 测试234234234324324234243299
showBtn: true
ofdConvertNew:
byStep:
step:
xsyxsyxsy: xsyjfie95555
stubdp:
xsy: gf555555
stubb:
pom: 45
mysql: 15655555
taskPool:
syncSsdrTask:
keepAliveSeconds: 999888
上述配置解释
-
allConfigKey
表示所有服务器都一样的配置,即所有服务器都需要这些配置
-
includeConfigKey
表示这个指定的服务器需要配置这些。例如上面这个就表示provinceCode是310000或540000的服务器需要的配置
-
excludeConfigKey
表示除了这几台服务器以外的服务器都需要的配置
例如:上面这个就表示320000的服务器不需要配置
bj: 省份320000这个配置不会生效
这个数据。540000和520000的服务器不需要配置
notHas: 除了540000,520000之外的省份这个配置会生效
配置,但是320000的服务器是需要这个配置的。
知道了这些之后,只需要做一些逻辑判断就可以了。
主程序
/**
* 修改yaml中属性的值
*
* @param sourceYml 源配置
* @param updateConfigYml 需要修改的配置
* @return true 修改成功,false 修改失败。
*/
public boolean updateYamlByMap(String sourceYml, String updateConfigYml) {
// 读取源配置文件
Map<String, Object> sourceConfigMap = this.getYamlToMap(sourceYml);
if (null == sourceConfigMap) {
System.out.println("源配置文件未找到");
return false;
}
if (MapUtils.isEmpty(sourceConfigMap)) {
System.out.println("源配置文件为空");
return false;
}
// 读取待修改配置项
Map<String, Object> updateConfigMap = this.getYamlToMap(updateConfigYml);
if (null == updateConfigMap) {
System.out.println("待配置文件未找到");
return false;
}
if (MapUtils.isEmpty(updateConfigMap)) {
System.out.println("待配置文件为空");
return true;
}
// 获取当前省份信息
String provinceCode;
System.out.println("当前省份编码是:" + provinceCode);
System.out.println("\n==========开始更新配置==========");
Map<String, Object> includeConfigMap = null;
Map<String, Object> excludeConfigMap = null;
Map<String, Object> allConfigMap = null;
if (updateConfigMap.containsKey("includeConfigKey")) {
includeConfigMap = (Map<String, Object>) updateConfigMap.get("includeConfigKey");
}
if (updateConfigMap.containsKey("excludeConfigKey")) {
excludeConfigMap = (Map<String, Object>) updateConfigMap.get("excludeConfigKey");
}
if (updateConfigMap.containsKey("allConfigKey")) {
allConfigMap = (Map<String, Object>) updateConfigMap.get("allConfigKey");
}
Yaml yaml = new Yaml(OPTIONS);
try {
// 先备份原配置文件
String bakFilePath = sourceYml + DateFormatUtils.format(new Date(), "yyyy_MM_dd-HH_mm_ss") + ".bak";
System.out.println("\n==========1.备份源文件==========");
System.out.println("源文件备份文件是:" + bakFilePath);
yaml.dump(sourceConfigMap, new OutputStreamWriter(new FileOutputStream(bakFilePath), StandardCharsets.UTF_8));
// 处理公共配置
System.out.println("\n==========2.处理公共配置==========");
if (MapUtils.isNotEmpty(allConfigMap)) {
mergeMap(sourceConfigMap, allConfigMap);
}
// 处理指定省份的配置
System.out.println("\n==========3.处理指定省份的配置==========");
if (MapUtils.isNotEmpty(includeConfigMap)) {
includeConfigMap.forEach((pCodes, value) -> {
if (value instanceof Map && StringUtils.isNotBlank(pCodes)) {
Map<String, Object> temp = (Map<String, Object>) value;
String[] codes = pCodes.split(",");
if (ArrayUtils.contains(codes, provinceCode)) {
mergeMap(sourceConfigMap, temp);
}
}
});
}
// 处理指定排除指定省份之外的配置
System.out.println("\n==========4.处理指定排除指定省份之外的配置==========");
if (MapUtils.isNotEmpty(excludeConfigMap)) {
excludeConfigMap.forEach((pCodes, value) -> {
Map<String, Object> temp = (Map<String, Object>) value;
String[] codes = pCodes.split(",");
if (!ArrayUtils.contains(codes, provinceCode)) {
mergeMap(sourceConfigMap, temp);
}
});
}
System.out.println("\n==========5.写入文件==========");
yaml.dump(sourceConfigMap, new OutputStreamWriter(new FileOutputStream(sourceYml), StandardCharsets.UTF_8));
return true;
} catch (Exception e) {
System.out.println("修改配置出现异常:e=" + e);
}
return false;
}
运行结果
IDE运行结果
运行jar包执行结果