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.pswp.cn/news/207629.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/207629.shtml
英文地址,請注明出處:http://en.pswp.cn/news/207629.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

TCP傳輸層詳解(計算機網絡復習)

介紹&#xff1a;TCP/IP包含了一系列的協議&#xff0c;也叫TCP/IP協議族&#xff0c;簡稱TCP/IP。該協議族提供了點對點的連接機制&#xff0c;并將傳輸數據幀的封裝、尋址、傳輸、路由以及接收方式都予以標準化 TCP/IP的分層模型 在講TCP/IP協議之前&#xff0c;首先介紹一…

力扣貪心題解 跳躍游戲

55. 跳躍游戲 - 力扣&#xff08;LeetCode&#xff09; 給你一個非負整數數組 nums &#xff0c;你最初位于數組的 第一個下標 。數組中的每個元素代表你在該位置可以跳躍的最大長度。 判斷你是否能夠到達最后一個下標&#xff0c;如果可以&#xff0c;返回 true &#xff1b…

信息系統開發方法

企業信息系統對于企業信息化的重要意義是不言而喻的。從實際運行的效果來看&#xff0c;有些信息系統運行得很成功&#xff0c;取得了巨大的經濟效益和社會效益&#xff1b;但也有些信息系統效果并不顯著&#xff0c;甚至還有個別信息系統開始時還能正常運行&#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盤里…

RestTemplate硬編碼的使用

RestTemplate是由Spring框架提供的一個可用于應用中調用rest服務的類它簡化了與http服務的通信方式&#xff0c;統一了RESTFul的標準&#xff0c;封裝了http連接&#xff0c;我們只需要傳入url及其返回值類型即可。相較于之前常用的HttpClient&#xff0c;RestTemplate是一種更…

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…

RepidJson中Writer類、FilewriteStream類、 PrettyWriter類的區別

rapidjson是一個C的JSON解析庫&#xff0c;可以用于解析和序列化JSON數據。 Writer是rapidjson中一種基本的輸出流&#xff0c;用于將JSON數據輸出到字符串或文件中。 FileWriteStream是一個Writer的子類&#xff0c;它專門用于將JSON數據輸出到文件中。相比于普通的Writer&a…

平臺工程文化:軟件開發的創新路徑和協作之道

在快速發展的軟件開發領域&#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…