java递归实现多级Map集合合并(结合实际场景)

合并Map集合

合并Map集合有很多方法,例如Map自生提供的putAll()方法。但是这种方法只能合并一层的集合,如果是多层的呢?

场景

现在有一个yml配置文件,由于项目部署在多台服务器上,而且每台服务器上的配置有些许差异。每次系统升级的时候都需要手动去配置。

这次想着写个程序给我们手动配置。

方案

需要一个源配置文件application.yml,每台服务器都有自己的配置文件。

需要开发人员给一个changeApplication.yml,这个配置文件里需要按一定的规则写好配置。

开发一个程序,打个jar包,运行jar包后把changeApplication.yml中的数据和合并到application.yml这个过程实际上就是两个多级Map集合的合并。

实现

读取yml程序的工具类Yml需要引入依赖

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.27</version>
</dependency>

读取yml文件,并转换成Map集合。Yml需要配置一些参数:

private final static DumperOptions OPTIONS = new DumperOptions();
static {
    // 设置yaml读取方式为块读取
    OPTIONS.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);  // 默认值是DumperOptions.FlowStyle.AUTO,不设置的话程序也可以自动识别。
    OPTIONS.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN);  // 默认值就是这个,可以不设置
    OPTIONS.setPrettyFlow(false);  // 默认值就是这个,可以不设置
}

FlowStyle源码,这个参数的解释是:块样式BLOCK是用缩进来表示文档中的嵌套和范围。 流样式FLOW依赖于明确的指示符来表示嵌套和范围。
对于yml文档来说,使用块样式更合适一些。

/**
* Block styles use indentation to denote nesting and scope within the document. In contrast, flow styles rely on explicit indicators to denote nesting and scope.
*/
public enum FlowStyle {
        FLOW(Boolean.TRUE), BLOCK(Boolean.FALSE), AUTO(null);

        private Boolean styleBoolean;

        private FlowStyle(Boolean flowStyle) {
            styleBoolean = flowStyle;
        }
        
        /*
         * Convenience for legacy constructors that took {@link Boolean} arguments since replaced by {@link FlowStyle}.
         * Introduced in v1.22 but only to support that for backwards compatibility.
         * @deprecated Since restored in v1.22.  Use the {@link FlowStyle} constants in your code instead.
         */
        @Deprecated
        public static FlowStyle fromBoolean(Boolean flowStyle) {
            return flowStyle==null ? AUTO
                : flowStyle ? FLOW
                    : BLOCK;
        }

        public Boolean getStyleBoolean() {
            return styleBoolean;
        }

        @Override
        public String toString() {
            return "Flow style: '" + styleBoolean + "'";
        }
    }

使用Yml读取yml配置文件。

/**
 * 将yaml配置文件转化成map
 * @param fileName
 * @return
 */
public Map<String, Object> getYamlToMap(String fileName) {
    LinkedHashMap<String, Object> yamls;
    Yaml yaml = new Yaml(OPTIONS);
    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包执行结果
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大骨熬汤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值