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

news2024/11/26 19:48:38

合并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包执行结果
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ajax(四)

1.模板引擎 1.1 模板引擎的基本概念 1.2 什么是模板引擎 不需要再用字符串拼接 1. 3模板引擎的好处 1.4 art-template模板引擎 art-template 是一个简约、超快的模板引擎。 中文官网首页为 http://aui.github.io/art-template/zh-cn/index.html 1.4.1 art-template模板引擎的…

为什么Python现在这么火?

Python可以说是目前最火的网红编程语言&#xff0c;虽然它在近几年在逐渐流行起来&#xff0c;但其实它已经发展了近三十年。那么&#xff0c;为什么Python现在这么火呢&#xff1f;一方面人工智能和大数据的崛起带红了Python&#xff0c;另一方面无论是软件开发者还是非编程工…

抗肿瘤的靶向药物——艾美捷西妥昔单抗Cetuximab说明

近年来恶性肿瘤极大的治疗进展是靶向新药的开发与使用。表皮生长因子受体EGFR是一种具有酪氨酸激酶活性的跨膜受体&#xff0c;受表皮生长因子EGF和转化生长因子-α(TGF-α)的刺激。多种肿瘤细胞株过度表达EGFR&#xff0c;包括25%&#xff5e;80%的结直肠癌CRC细胞。 西妥昔单…

RSS Can:使用 Golang Rod 解析浏览器中动态渲染的内容:(四)

第四篇文章&#xff0c;来聊聊 Golang 生态中如何“遥控”浏览器&#xff0c;更简单、可靠的使用基于 CDP &#xff08;Chrome DevTools Protocol&#xff09;协议的浏览器作为容器&#xff0c;获取诸如微博、B 站 这类动态渲染内容信息&#xff0c;将它们转换为 RSS 订阅源。 …

【C语言进阶】不会处理字符串?一万三千五百字包会保姆级教程

目录 &#x1f618;前言&#x1f618;&#xff1a; 一、字符串处理函数介绍&#x1f92f;&#xff1a; 1.strlen 函数&#x1f94e;&#xff1a; 2.strcpy 函数⚾&#xff1a; 3.strcat 函数&#x1f3c0;&#xff1a; 4.strcmp 函数&#x1f3c8;&#xff1a; 5.strncpy 函数…

WSL_03 WSL2 从C盘迁移到D盘

文章目录1 动机1 查看虚拟机状态&#xff0c;并关闭要迁移的虚拟机2 迁移WSL22.1 出现的问题&#xff1a;已存在具有提供的名称的分发(已解决)3 设置启动时的默认用户&#xff0c;没有设置默认为root参考1 动机 WSL2默认安装在C盘中&#xff0c;win R运行中使用%localappdata…

python科学计算 之 Numpy库的使用详解

目录 一&#xff1a;Numpy简介 二&#xff1a;ndarray的创建 三&#xff1a;ndarray的属性 四&#xff1a;切片 索引 五&#xff1a;数组的轴(二维数组) 六&#xff1a;二维数组 切片索引 七&#xff1a;高级索引 八&#xff1a;Numpy广播 九&#xff1a;ufunc函数 算…

使用pypy来提升你的python项目性能

一、PyPy介绍 PyPy是用Python实现的Python解释器的动态编译器&#xff0c;是Armin Rigo开发的产品&#xff0c;能够提升我们python项目的运行速度。PyPy 是利用即时编译的 Python 的替代实现。背后的原理是 PyPy 开始时就像一个解释器&#xff0c;直接从源文件运行我们的 Pyth…

Revit二次开发小技巧(十五)构件的最小矩形外轮廓

前言&#xff1a;我们会经常遇到需要计算一个构件的最小外轮廓&#xff0c;一般直接取BoundingBox只有最大和最小值坐标&#xff0c;也是基于x-y坐标系下的。往往不是最小的矩形&#xff0c;所以分享下面的算法来计算最小的外轮廓&#xff0c;条件为法向量是指向Z轴的&#xff…

mqtt服务器搭建与qt下的mqtt客户端实现

一、mqtt介绍 MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的&#xff0c;这些特点使它适用范围非常广泛。在很多情…

前端基础(九)_CSS的三大特征

CSS的三大特征 1、层叠性 1.样式冲突&#xff0c;遵循就近原则 2.样式不冲突&#xff0c;不会层叠&#xff0c;会叠加 1.1.样式冲突&#xff0c;遵循就近原则例子&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UT…

[附源码]Nodejs计算机毕业设计基于的服装商城系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

Java优雅的记录日志:log4j实战篇

写在前面 项目开发中&#xff0c;记录错误日志有以下好处&#xff1a; 方便调试 便于发现系统运行过程中的错误 存储业务数据&#xff0c;便于后期分析 在java中&#xff0c;记录日志有很多种方式&#xff1a; 自己实现&#xff1a;自己写类&#xff0c;将日志数据&#xf…

HTTP Range:范围请求

文章目录HTTP 范围请求HTTP 范围请求 Range 头是在 HTTP/1.1 协议中新增的一个请求头。包含 Range 头的请求通常称为范围请求&#xff0c;因为 Range 头允许服务器只发送部分响应到客户端&#xff0c;它是下载工具&#xff08;例如迅雷&#xff09;实现多线程下载的核心所在&a…

Python -- 列表

目录 1.列表的基本使用 1.1 列表的格式 1.2 使用下标获取列表元素 2.列表的增删改查 2.1 添加元素 2.2 修改元素 2.3 查找元素 2.4 删除元素 2.5 排序&#xff08;sort、reverse&#xff09; 3.列表遍历 3.1 使用while循环 3.2 使用for循环 4.列表的嵌套 1.列表的基本…

面向切面编程 AOP

AOPAOP的概念AOP &#xff08;底层原理&#xff09;AOP 底层使用动态代理AOP &#xff08; JDK 动态代理&#xff09;首先我们来看一下 Spring 的百度百科   Spring 框架是一个开放源代码的 J2EE 应用程序框架&#xff0c;由 Rod Johnson 发起&#xff0c;是针对 Bean 的生命…

cpp项目中遇到的一些错误

1.解决由于找不到xxx.dll&#xff0c;无法继续执行代码的问题 解决由于找不到xxx.dll&#xff0c;无法继续执行代码的问题_happylife_mini的博客-CSDN博客_由于找不到emp.dll无法继续执行代码在用vs写项目&#xff0c;或者你下载github上的C代码的时候&#xff0c;是不是经常遇…

【Redis技术探索】「底层架构原理」探索分析服务系统的网络架构和线程模型

Redis网络基础架构 网络编程离不开Socket&#xff0c;网络I/O模型最常用的无非是同步阻塞、同步非阻塞、异步阻塞、异步非阻塞&#xff0c;高性能网络服务器最常见的线程模型也就是基于EventLoop模式的单线程模型。 Redis基础组建结构 Redis网络层基础组件主要包括四个部分&a…

acm是什么?你准备好去打了吗?(未完结)

1.引言2.acm究竟是什么&#xff1f;3.acm的时间安排4.acm该如何准备1.引言 作为一个零基础的小白&#xff0c;acm这条路走的并不顺畅&#xff0c;接触的信息很少&#xff0c;以至于在这条道路上走了不少弯路&#xff0c;浪费了大量的时间&#xff0c;现在也快要退役的阶段&…

Linux基础-软件包管理器RPM与yum

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【参考文章】 参考文章&#xff1a;https://howard2005.blog.csdn.net/article/details/127131286?spm1001.2014.3001.5502 文章目录一、使用RPM软件包管理器1. RPM安装软件包2. RPM更新与升级软件包3. RPM查询软件包…