snakeyaml编辑yaml文件并覆盖注释

文章目录

    • 前言
    • 技术积累
    • 实战演示
      • 1、引入maven依赖
      • 2、覆盖注释工具类
      • 3、snakeyaml工具类
      • 4、测试用例
      • 5、测试效果展示
    • 写在最后

前言

最近在做一个动态整合框架的项目,需要根据需求动态组装各个功能模块。其中就涉及到了在application.yaml中加入其他模块的配置,这里我们采用了snakeyaml进行配置信息写入,并采用文件回写保证注释不丢失。

技术积累

SnakeYaml就是用于解析YAML,序列化以及反序列化的第三方框架,解析yml的三方框架有很多,SnakeYaml,jYaml,Jackson等,但是不同的工具功能还是差距较大,比如jYaml就不支持合并。

SnakeYaml是一个完整的YAML1.1规范Processor,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型。

SnakeYaml官方地址:http://yaml.org/type/index.html

在这里插入图片描述

实战演示

1、引入maven依赖

<!--yaml编辑-->
<dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.23</version>
</dependency>
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version>
</dependency>

2、覆盖注释工具类

由于snakeyaml在操作文件时候,会先将yaml转为map然后再回写到文件,这个操作会导致注释丢失。
目前有效的方案是将修改前文件注释进行缓存,然后当业务操作完文件后进行注释会写,这样就能够保证注释不会被覆盖。

当然,目前的方案并没有增加新的配置文件注释写入功能,有需要的同学可以自己实现。大概的思路是根据在回写注释的时候根据key将新增的注释写入,此时需要注释多个key相同的情况,故需要判断全链路key以防止重复注释乱序。

package com.example.demo.utils;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** CommentUtils* @author senfel* @version 1.0* @date 2023/12/6 18:20*/
public class CommentUtils {public static final String END = "END###";public static final Pattern COMMENT_LINE = Pattern.compile("^\\s*#.*$");public static final Pattern BLANK_LINE = Pattern.compile("^\\s*$");//带注释的有效行,  使用非贪婪模式匹配有效内容public static final Pattern LINE_WITH_COMMENT = Pattern.compile("^(.*?)\\s+#.*$");@Data@AllArgsConstructorpublic static class Comment {private String lineNoComment;private String lineWithComment;private Integer indexInDuplicates;    // 存在相同行时的索引 (不同key下相同的行, 如 a:\n name: 1  和  b:\n name: 1 )private boolean isEndLine() {return END.equals(lineNoComment);}}@SneakyThrowspublic static CommentHolder buildCommentHolder(File file) {List<Comment> comments = new ArrayList<>();Map<String, Integer> duplicatesLineIndex = new HashMap<>();CommentHolder holder = new CommentHolder(comments);List<String> lines = FileUtils.readLines(file, StandardCharsets.UTF_8);// 末尾加个标志, 防止最后的注释丢失lines.add(END);StringBuilder lastLinesWithComment = new StringBuilder();for (String line : lines) {if (StringUtils.isBlank(line) || BLANK_LINE.matcher(line).find()) {lastLinesWithComment.append(line).append('\n');continue;}// 注释行/空行 都拼接起来if (COMMENT_LINE.matcher(line).find()) {lastLinesWithComment.append(line).append('\n');continue;}String lineNoComment = line;boolean lineWithComment = false;// 如果是带注释的行, 也拼接起来, 但是记录非注释的部分Matcher matcher = LINE_WITH_COMMENT.matcher(line);if (matcher.find()) {lineNoComment = matcher.group(1);lineWithComment = true;}// 去除后面的空格lineNoComment = lineNoComment.replace("\\s*$", "");// 记录下相同行的索引Integer idx = duplicatesLineIndex.merge(lineNoComment, 1, Integer::sum);// 存在注释内容, 记录if (lastLinesWithComment.length() > 0 || lineWithComment) {lastLinesWithComment.append(line);comments.add(new Comment(lineNoComment, lastLinesWithComment.toString(), idx));// 清空注释内容lastLinesWithComment = new StringBuilder();}}return holder;}@AllArgsConstructorpublic static class CommentHolder {private List<Comment> comments;/*** 通过正则表达式移除匹配的行 (防止被移除的行携带注释信息, 导致填充注释时无法正常匹配)*/public void removeLine(String regex) {comments.removeIf(comment -> comment.getLineNoComment().matches(regex));}/*** fillComments* @param file* @author senfel* @date 2023/12/7 11:24* @return void*/@SneakyThrowspublic void fillComments(File file) {if (comments == null || comments.isEmpty()) {return;}if (file == null || !file.exists()) {throw new IllegalArgumentException("file is not exist");}List<String> lines = FileUtils.readLines(file, StandardCharsets.UTF_8);Map<String, Integer> duplicatesLineIndex = new HashMap<>();int comIdx = 0;StringBuilder res = new StringBuilder();for (String line : lines) {Integer idx = duplicatesLineIndex.merge(line, 1, Integer::sum);Comment comment = getOrDefault(comments, comIdx, null);if (comment != null &&Objects.equals(line, comment.lineNoComment)&& Objects.equals(comment.indexInDuplicates, idx)) {res.append(comment.lineWithComment).append('\n');comIdx++;} else {res.append(line).append('\n');}}Comment last = comments.get(comments.size() - 1);if (last.isEndLine()) {res.append(last.lineWithComment.substring(0, last.lineWithComment.indexOf(END)));}FileUtils.write(file, res.toString(), StandardCharsets.UTF_8);}}public static <T> T getOrDefault(List<T> vals, int index, T defaultVal) {if (vals == null || vals.isEmpty()) {return defaultVal;}if (index >= vals.size()) {return defaultVal;}T v = vals.get(index);return v == null ? defaultVal : v;}}

3、snakeyaml工具类

snakeyaml工具类主要作用就是将yaml文件转为map的格式,然后依次进行判断写入或者修改value。

package com.example.demo.utils;import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Map;/*** YamlActionUtils * @author senfel* @version 1.0* @date 2023/12/7 13:48*/
public class YamlActionUtils {/*** 配置* @author senfel* @date 2023/12/7 13:49* @return*/private static DumperOptions dumperOptions = new DumperOptions();static{//设置yaml读取方式为块读取dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);dumperOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN);dumperOptions.setPrettyFlow(false);}/*** insertYaml* @param key a.b.c* @param value* @param path* @author senfel* @date 2023/12/7 10:11* @return boolean*/public static boolean insertYaml(String key, Object value, String path) throws Exception {Yaml yaml = new Yaml(dumperOptions);String[] keys = key.split("\\.");int len = keys.length;//将属性转为mapFileInputStream fileInputStream = new FileInputStream(new File(path));Map<String, Object> yamlToMap = (Map<String, Object>)yaml.load(fileInputStream);Object oldVal = getValue(key, yamlToMap);//找到key不再新增if (null != oldVal) {return true;}Map<String,Object> temp = yamlToMap;for (int i = 0; i < len - 1; i++) {if (temp.containsKey(keys[i])) {temp = (Map) temp.get(keys[i]);} else {temp.put(keys[i],new HashMap<String,Object>());temp =(Map)temp.get(keys[i]);}if (i == len - 2) {temp.put(keys[i + 1], value);}}try {yaml.dump(yamlToMap, new FileWriter(path));} catch (Exception e) {System.out.println("yaml file insert failed !");return false;}return true;}/*** updateYaml* @param paramKey a.b.c* @param paramValue* @param path* @author senfel* @date 2023/12/7 10:03* @return boolean*/public static boolean updateYaml(String paramKey, Object paramValue,String path) throws Exception{Yaml yaml = new Yaml(dumperOptions);//yaml文件路径String yamlUr = path;Map map = null;try {//将yaml文件加载为map格式map = yaml.loadAs(new FileInputStream(yamlUr), Map.class);} catch (FileNotFoundException e) {e.printStackTrace();}//获取当前参数值并且修改boolean flag = updateYaml(paramKey, paramValue, map, yamlUr, yaml);return flag;}/*** updateYaml* @param key a.b.c* @param value* @param yamlToMap* @param path* @param yaml* @author senfel* @date 2023/12/7 10:51* @return boolean*/public static boolean updateYaml(String key, Object value, Map<String, Object> yamlToMap, String path, Yaml yaml) {Object oldVal = getValue(key, yamlToMap);//未找到key 不修改if (null == oldVal) {return false;}try {Map<String, Object> resultMap = setValue(yamlToMap, key, value);if (resultMap != null) {yaml.dump(resultMap, new FileWriter(path));return true;} else {return false;}} catch (Exception e) {System.out.println("yaml file update failed !");}return false;}/*** getValue* @param key a.b.c* @param yamlMap* @author senfel* @date 2023/12/7 10:51* @return java.lang.Object*/public static Object getValue(String key, Map<String, Object> yamlMap) {String[] keys = key.split("[.]");Object o = yamlMap.get(keys[0]);if (key.contains(".")) {if (o instanceof Map) {return getValue(key.substring(key.indexOf(".") + 1), (Map<String, Object>) o);} else {return null;}} else {return o;}}/*** setValue* @param map* @param key a.b.c* @param value* @author senfel* @date 2023/12/7 9:59* @return java.util.Map<java.lang.String, java.lang.Object>*/public static Map<String, Object> setValue(Map<String, Object> map, String key, Object value) {String[] keys = key.split("\\.");int len = keys.length;Map temp = map;for (int i = 0; i < len - 1; i++) {if (temp.containsKey(keys[i])) {temp = (Map) temp.get(keys[i]);} else {return null;}if (i == len - 2) {temp.put(keys[i + 1], value);}}for (int j = 0; j < len - 1; j++) {if (j == len - 1) {map.put(keys[j], temp);}}return map;}}

4、测试用例

我们分别新增、修改yaml文件进行测试。

package com.example.demo;import com.example.demo.utils.CommentUtils;
import com.example.demo.utils.YamlActionUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.File;/*** YamlActionTest* @author senfel* @version 1.0* @date 2023/12/6 17:55*/
@SpringBootTest
public class YamlActionTest {@Testpublic void addKey() throws Exception{String filePath = "D:\\workspace\\demo\\src\\main\\resources\\application.yaml";File file = new File(filePath);//记录yaml文件的注释信息CommentUtils.CommentHolder holder = CommentUtils.buildCommentHolder(file);//YamlActionUtils.insertYaml("spring.activemq.broker-url","http://127.0.0.1/test",filePath);//YamlActionUtils.insertYaml("spring.activemq.pool.enabled",false,filePath);YamlActionUtils.insertYaml("wx.pc.lx.enable",false,filePath);//YamlActionUtils.insertYaml("spring.activemq.in-memory",false,filePath);//YamlActionUtils.updateYaml("spring.activemq.in-memory",false,filePath);//填充注释信息holder.fillComments(file);}
}

5、测试效果展示

server:port: 8888
spring:activemq:close-timeout: 15 #超时broker-url: http://127.0.0.1/test #路径pool:enabled: false # 是否开启
wx:pc:lx:enable: false

如上所示 wx.pc.lx.enable=false已经写入。

写在最后

snakeyaml编辑yaml文件并覆盖注释还是比较简单,大致就是在操作yaml文件之前对注释进行缓存,操作文件时先将yaml转为map,然后配置数据写入并转换成yaml文件,最后再将注释覆盖在yaml上即可。

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

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

相关文章

TCP传输层详解(计算机网络复习)

介绍&#xff1a;TCP/IP包含了一系列的协议&#xff0c;也叫TCP/IP协议族&#xff0c;简称TCP/IP。该协议族提供了点对点的连接机制&#xff0c;并将传输数据帧的封装、寻址、传输、路由以及接收方式都予以标准化 TCP/IP的分层模型 在讲TCP/IP协议之前&#xff0c;首先介绍一…

广州数字孪生赋能工业制造,加速推进制造业数字化转型

广州数字孪生赋能工业制造&#xff0c;加速推进制造业数字化转型。数字孪生系统基于历史数据、实时数据&#xff0c;采用人工智能、大数据分析等新一代信息技术对物理实体的组成、特征、功能和性能进行数字化定义和建模。通过构建在信息世界对物理实体的等价映射&#xff0c;对…

Axure官方软件安装、汉化保姆级教程(带官方资源下载)

1.下载汉化包 百度云链接&#xff1a;https://pan.baidu.com/s/1lluobjjBZvitASMt8e0A_w?pwdjqxn 提取码&#xff1a; jqxn 2.解压压缩包 3.安装Axure 进行安装 点击next 打勾&#xff0c;然后next, 默认是c盘&#xff0c;修改成自己的文件夹&#xff08;不要什么都放c盘里…

API测试基础之http协议

http简介&#xff1a; http&#xff08;超文本传输协议&#xff09;是一个简单的请求-响应协议&#xff0c;它通常运行在TCP&#xff08;传输控制协议&#xff09;之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII码形式给出…

远程控制如何赋能智能制造?贝锐向日葵制造业场景案例解析

随着数字化转型在制造业的不断深入&#xff0c;企业在产线端也逐渐投入更多智能化设备&#xff0c;数字化、智能化设备其中一个比较显著的优势就是可以依托互联网实现远程运维和调试&#xff0c;大大提升产线设备的稳定性和工作效率&#xff1b;而远程调试运维一个重要的实现方…

人工智能原理复习--搜索策略(一)

文章目录 上一篇搜索概述一般图搜索盲目搜索下一篇 上一篇 人工智能原理复习–确定性推理 搜索概述 问题求解分为两大类&#xff1a;知识贫乏系统&#xff08;依靠搜索技术解决&#xff09;、知识丰富系统&#xff08;依靠推理技术&#xff09; 两大类搜索技术&#xff1a; …

海思3516DV500下的目标识别算法运行评估,包含yolov7,yolov8

目前在3516DV500下&#xff0c;自己训练的模型的评估实测结果。根据实际模型会有些许差异。 涉及到技术细节的部分因为商业用途&#xff0c;有部分省略。如需相关技术服务项目合作可私信联系。 我司推出的目标识别跟踪模块&#xff0c;支持热红外、可见光主流多光谱视频输入与目…

WeiPHP 微信开发平台 SQL注入漏洞复现

0x01 产品简介 weiphp 是一个开源,高效,简洁的微信开发平台,基于 oneThink 内容管理框架实现。 0x02 漏洞概述 weiphp 微信开发平台 _send_by_group、 wp_where、 get_package_template等接口处存在 SQL 注入漏洞,攻击者利用此漏洞可获取数据库中的信息(例如,管理员后台…

三数组最小距离:2020年408算法题

算法思想 算法实现 #define INT_MAX 0x7fffffff //c语言int类型最大值 //计算绝对值 int abs(int a){if(a<0) return -a;else return a; } //判断a是否为3个数中最小值 bool isMin(int a,int b,int c){if(a<b&&a<c) return true;return false; }//主函数 in…

平台工程文化:软件开发的创新路径和协作之道

在快速发展的软件开发领域&#xff0c;具有前瞻性思维的企业组织正在拥抱平台工程文化的变革力量。这种创新方法强调创建共享平台、工具和实践&#xff0c;使开发人员能够更快、更高效地交付高质量的软件。在本文中&#xff0c;我们将深入探讨平台工程文化的核心原则和深远的好…

C语言期末考试复习PTA数据类型及表达式-分支结构程序-循环结构-数组经典选择题

目录 第一章&#xff1a;C语言数据类型和表达式 第一题&#xff1a; 第二题&#xff1a; 第三题&#xff1a; 第四题&#xff1a; 第五题&#xff1a; 第六题&#xff1a; 第七题&#xff1a; 第八题&#xff1a; 第九题&#xff1a; 第二章&#xff1a;分支结构程序…

打包 抖音直播云游戏

抖音直播云游戏 oaid资源中的bcpkix-jdk15to18-1.68.jar与抖音云游戏的资源冲突。 其实资源名称是一样的&#xff0c;拷贝时资源名称有变化。 为解决此问题&#xff0c;需要规范化文件的资源名称&#xff0c;将.置为_ Error: Command failed: cmd /c echo off && Chc…

NoSuchColumnFamilyException: org.apache.hadoop.hbase.regionserv

问题 在IDEA运行HBASE脚本时出现如下报错&#xff1a; org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException: org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException: Column family table does not exist in region hbase:meta,,1.1588230740 i…

Java多线程并发(二)

四种线程池 Java 里面线程池的顶级接口是 Executor&#xff0c;但是严格意义上讲 Executor 并不是一个线程池&#xff0c;而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。 newCachedThreadPool 创建一个可根据需要创建新线程的线程池&#xff0c;但是在以前…

深入了解数据库锁:类型、应用和最佳实践

目录 1. 引言 2. 数据库锁的基本概念 2.1 悲观锁和乐观锁 2.2 排他锁和共享锁 3. 悲观锁的应用场景 3.1 长事务和大事务 3.2 并发修改 3.3 数据库死锁 4. 悲观锁的最佳实践 4.1 精细控制锁的粒度 4.2 避免死锁 4.3 考虑乐观锁 5. 案例分析 5.1 银行系统的转账操作…

【GEE笔记】随机森林特征重要性计算并排序

随机森林是一种基于多个决策树的集成学习方法&#xff0c;可以用于分类和回归问题。在gee中可以使用ee.Classifier.smileRandomForest()函数来创建一个随机森林分类器&#xff0c;并用它来对影像进行分类。 随机森林分类器有一个重要的属性&#xff0c;就是可以计算每个特征&a…

人工智能虚拟化环境

人工智能虚拟化环境通过模拟、管理和优化计算资源、数据资源和软件环境&#xff0c;可以为人工智能算法和应用提供更加高效、灵活和可靠的运行平台。本文将探讨人工智能虚拟化环境的概念、技术和应用&#xff0c;并展望其在人工智能领域的未来发展。 首先&#xff0c;人工智能…

LVGL的学习

该LVGL基于LVGL的8.2版本 开关的控件Demo lv_obj_t* switch_obj lv_switch_create(lv_scr_act());lv_obj_set_size(switch_obj, 120, 60);lv_obj_align(switch_obj, LV_ALIGN_CENTER, 0, 0); 对象&#xff1a; 对于这一类对象&#xff0c;他们有共同的属性的几个特征。 创建部…

.NET使用分布式网络爬虫框架DotnetSpider快速开发爬虫功能

前言 前段时间有同学在微信群里提问&#xff0c;要使用.NET开发一个简单的爬虫功能但是没有做过无从下手。今天给大家推荐一个轻量、灵活、高性能、跨平台的分布式网络爬虫框架&#xff08;可以帮助 .NET 工程师快速的完成爬虫的开发&#xff09;&#xff1a;DotnetSpider。 注…

Vue3组件使用问题

Vue3组件学习 文章目录 Vue3组件学习一、Message 全局提示组件返回数据换行问题二、DatePicker 日期选择框组件限制选定年份问题 一、Message 全局提示组件返回数据换行问题 问题&#xff1a;使用中发现仅仅通过写入\n或<br/>&#xff0c;无法实现回车显示的结果。 解决…