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.hqwc.cn/news/258533.html

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

相关文章

探索创意无限的Photoshop CC 2020Mac/Win版

作为一款功能强大的图像处理软件&#xff0c;Photoshop CC 2020&#xff08;简称PS 2020&#xff09;在全球范围内备受设计师、摄影师和艺术家的喜爱和推崇。它不仅为用户提供了丰富多样的工具和功能&#xff0c;还不断推出新的创意特效和改进的功能&#xff0c;让用户的创意无…

动态规划——完全背包问题(公式推导,组合、排列)

本文章是对于完全背包 一些题型(如题目所示&#xff0c;组合、排列和最小值类型)的总结和理解&#xff0c;依次记录一下&#xff0c;方便回顾与复习。 本文章是基于个人所总结 实现的&#xff0c;但在其中遇到了一些疑惑与困难&#xff0c;所以总结一篇与完全背包相关的问题。 …

spring 的概述和入门

​ 我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; …

Python语言求解嵌套列表中的最大元素和

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在处理嵌套列表时&#xff0c;有时我们需要找到列表中的最大元素以及对应的位置。本文将深入讨论如何使用Python有效地解决这个问题。我们将使用不同的方法&#xff0c;包括递归、列表推导和NumPy库&#xff0c;…

微信小程序基础

1.小程序发展史 微信小程序之前&#xff0c;是使用weixin-sdk进行开发&#xff0c;调用视频&#xff0c;摄像头等。 微信小程序weixin up端&#xff0c;所以PC端的window这些没有&#xff0c;运行环境是IOS&#xff0c;安卓等&#xff0c;有一些特殊的调用录音功能&#xff0…

【以医院为案例】讲如何画业务架构图

【以医院为案例】讲如何画业务架构图 背景知识 什么是业务&#xff1f; 业务是个人或企业为了获利而进行的有组织的努力和活动。 以医院为案例: 业务是指医院提供医疗服务的活动。患者通过消费来享受医院提供的医疗服务&#xff0c;从而重新获得健康的身体。可以将患者去医院…

实战| 通杀漏洞挖掘技巧

前言 前端时间&#xff0c;要开放一个端口&#xff0c;让我进行一次安全检测&#xff0c;发现的一个漏洞。 经过 访问之后发现是类似一个目录索引的端口。(这里上厚码了哈) 错误案例测试 乱输内容asdasffda之后看了一眼Burp的抓包&#xff0c;抓到的内容是可以发现这是一个…

家用洗地机希亦、追觅和添可哪款好用?测评PK谁是清洁之王

对于上班族来说&#xff0c;时间非常宝贵&#xff0c;打扫卫生就成为了一件比较痛苦的事情。现在的都市上班族都会寄托于智能家电。在当前市场上&#xff0c;洗地机已成为家庭清洁的面部工具。洗地机是一种高效的清洁设备&#xff0c;以其自动化、高效率的清洁功能&#xff0c;…

express搭建后台node接口

在前端的学习中我们使用express来开发接口结合mysql&#xff0c;然后使用可视化的数据库工具来操作数据&#xff0c; web框架是express 文档是jsdoc swagger 数据库模型是sequelize 部署使用PM2来上服务器&#xff0c; 打包你也可以结合webpack配置target node状态 当然你也可以…

ES-环境安装(elasticsearch:7.17.9,kibana,elasticsearch-head)

ES 环境搭建 1 拉取镜像 常用三件套 docker pull kibana:7.17.9 docker pull elasticsearch:7.17.9 docker pull mobz/elasticsearch-head:52 启动镜像 elasticsearch 安装 这里可以先不挂载文件启动一波&#xff0c;然后把容器里的文件拷贝出来 docker run -p 19200:9200 …

【AI】以大厂PaaS为例,看人工智能技术方案服务能力的方向(2/2)

目录 三、解决方案 3.1 人脸身份验证 3.2 图像审核&#xff08;暴恐、色情等&#xff09; 3.3 人脸会场签到 3.4 机器人视觉 3.5 视频审核 3.6 电商图文详情生成 3.7 智能客服 接上回&#xff1a; 【AI】以大厂PaaS为例&#xff0c;看人工智能技术方案服务能力的方向&…

SSL 协议

SSL 是用于安全传输数据的一种通信协议。它采用公钥加密技术、对称密钥加密技术等保护两个应用之间的信息传输的机密性和完整性。但是&#xff0c;SSL 也有一个不足&#xff0c;就是它本身不能保证传输信息的不可否认性。 SSL 协议包括服务器认证、客户认证、SSL 链路上的数据完…