Yml轉(zhuǎn)properties文件工具類YmlUtils的詳細過程(不用引任何插件和依賴)
【誕生背景】
最近在做某配置中心的時候,配置中心采用properties格式進行配置的(如下圖)。

而我們工程的項目配置文件是yml格式的(如下圖)。

如果人為手動的一條一條,將yml文件中的配置數(shù)據(jù),添加到配置中心,難免會消耗大量的人力和精力,況且還容易輸入錯誤。因此,需要一個工具或插件,將 yml 文件的格式,轉(zhuǎn)換為properties文件。
【Convert YAML and Properties File 插件的不足】
IDEA 有一個插件叫 Convert YAML and Properties File, 于是,首先用了一下 這個插件后,發(fā)現(xiàn)了,發(fā)現(xiàn)這個插件不太友好,具體有以下幾點。

比如,現(xiàn)在我們有如下的 yml 配置文件:
我們用插件將它轉(zhuǎn)化為 properties 文件。

下面是轉(zhuǎn)化后的效果:

從這轉(zhuǎn)換后的效果,我們不難發(fā)現(xiàn)該插件有以下幾點問題:
(1)轉(zhuǎn)化后,原 yml 配置文件消失(如果轉(zhuǎn)出了問題,想看原配置文件,還看不了了);
(2)排序出現(xiàn)混亂,沒有按照原 yml 文件數(shù)據(jù)進行輸出(msg相關(guān)的配置本來在原yml文件中是第二個配置,轉(zhuǎn)換后卻成為了第一個;同理,mybatis的配置本是最后一個,轉(zhuǎn)化后卻放在了第二個);
(3)所有注釋均不見了(所有相關(guān)的注釋全都不見了,包括行級注釋和末尾注釋);
(4)某些值沒有進行配置,但轉(zhuǎn)化后,卻顯示為了 null 字符串(如 msg.sex 的配置);
(5)該插件僅IDEA有,Eclipse中還沒有,不能垮開發(fā)工具使用;
【自寫小工具 YmlUtils 實現(xiàn)】
針對上面 IDEA 插件的不足,于是自己寫了一款小工具 YmlUtils(源碼在文章結(jié)尾處 ),你可以將它放在工程中的任何位置。

現(xiàn)在,我們同樣以剛剛的 yml 配置文件為測試模板,來測試下這款小工具。

測試的方法很簡單,只需要將 yml 配置文件放在根目錄下,然后寫個 Test 類,調(diào)用里面的 castProperties 方法即可。
YmlUtils.castProperties("application-test.yml");

執(zhí)行方法后,首先我們可以看到控制臺會將 porperties 文件的內(nèi)容打印到控制臺上面:

程序運行完成后,根目錄下會多出一個與yml同名的properties文件,該文件就直接拷貝到相應(yīng)的地方進行使用,而且原文件也并未收到任何損壞和影響。

【源碼展示】
最后附上工具類源碼,如果對你有用,記得一鍵三連(支持原創(chuàng))。
package com.test.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Yaml 配置文件轉(zhuǎn) Properties 配置文件工具類
* @author https://zyqok.blog.csdn.net/
* @since 2021/08/24
*/
public class YmlUtils {
/**
* 將 yml 文件轉(zhuǎn)化為 properties 文件
*
* @param ymlFileName 工程根目錄下(非resources目錄)的 yml 文件名稱(如:abc.yml)
* @return List<Node> 每個Nyml 文件中每行對應(yīng)解析的數(shù)據(jù)
*/
public static List<YmlNode> castProperties(String ymlFileName) {
if (ymlFileName == null || ymlFileName.isEmpty() || !ymlFileName.endsWith(".yml")) {
throw new RuntimeException("請輸入yml文件名稱!!");
}
File ymlFile = new File(ymlFileName);
if (!ymlFile.exists()) {
throw new RuntimeException("工程根目錄下不存在 " + ymlFileName + "文件?。?);
}
String fileName = ymlFileName.split(".yml", 2)[0];
// 獲取文件數(shù)據(jù)
String yml = read(ymlFile);
List<YmlNode> nodeList = getNodeList(yml);
// 去掉多余數(shù)據(jù),并打印
String str = printNodeList(nodeList);
// 將數(shù)據(jù)寫入到 properties 文件中
String propertiesName = fileName + ".properties";
File file = new File(propertiesName);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try (FileWriter writer = new FileWriter(file)) {
writer.write(str);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
return nodeList;
}
/**
* 將yml轉(zhuǎn)化為porperties文件,并獲取轉(zhuǎn)化后的鍵值對
*
* @param ymlFileName 工程根目錄下的 yml 文件名稱
* @return 轉(zhuǎn)化后的 porperties 文件鍵值對Map
*/
public static Map<String, String> getPropertiesMap(String ymlFileName) {
Map<String, String> map = new HashMap<>();
List<YmlNode> list = castProperties(ymlFileName);
for (YmlNode node : list) {
if (node.getKey().length() > 0) {
map.put(node.getKey(), node.getValue());
}
}
return map;
}
private static String read(File file) {
if (Objects.isNull(file) || !file.exists()) {
return "";
}
try (FileInputStream fis = new FileInputStream(file)) {
byte[] b = new byte[(int) file.length()];
fis.read(b);
return new String(b, StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
private static String printNodeList(List<YmlNode> nodeList) {
StringBuilder sb = new StringBuilder();
for (YmlNode node : nodeList) {
if (node.getLast().equals(Boolean.FALSE)) {
continue;
}
if (node.getEmptyLine().equals(Boolean.TRUE)) {
System.out.println();
sb.append("\r\n");
continue;
}
// 判斷是否有行級注釋
if (node.getHeadRemark().length() > 0) {
String s = "# " + node.getHeadRemark();
System.out.println(s);
sb.append(s).append("\r\n");
continue;
}
// 判斷是否有行末注釋 (properties中注釋不允許末尾注釋,故而放在上面)
if (node.getTailRemark().length() > 0) {
String s = "# " + node.getTailRemark();
System.out.println(s);
sb.append(s).append("\r\n");
}
//
String kv = node.getKey() + "=" + node.getValue();
System.out.println(kv);
sb.append(kv).append("\r\n");
}
return sb.toString();
}
private static List<YmlNode> getNodeList(String yml) {
String[] lines = yml.split("\r\n");
List<YmlNode> nodeList = new ArrayList<>();
Map<Integer, String> keyMap = new HashMap<>();
Set<String> keySet = new HashSet<>();
for (String line : lines) {
YmlNode node = getNode(line);
if (node.getKey() != null && node.getKey().length() > 0) {
int level = node.getLevel();
if (level == 0) {
keyMap.clear();
keyMap.put(0, node.getKey());
} else {
int parentLevel = level - 1;
String parentKey = keyMap.get(parentLevel);
String currentKey = parentKey + "." + node.getKey();
keyMap.put(level, currentKey);
node.setKey(currentKey);
}
}
keySet.add(node.getKey() + ".");
nodeList.add(node);
}
// 標識是否最后一級
for (YmlNode each : nodeList) {
each.setLast(getNodeLast(each.getKey(), keySet));
}
return nodeList;
}
private static boolean getNodeLast(String key, Set<String> keySet) {
if (key.isEmpty()) {
return true;
}
key = key + ".";
int count = 0;
for (String each : keySet) {
if (each.startsWith(key)) {
count++;
}
}
return count == 1;
}
private static YmlNode getNode(String line) {
YmlNode node = new YmlNode();
// 初始化默認數(shù)據(jù)(防止NPE)
node.setEffective(Boolean.FALSE);
node.setEmptyLine(Boolean.FALSE);
node.setHeadRemark("");
node.setKey("");
node.setValue("");
node.setTailRemark("");
node.setLast(Boolean.FALSE);
node.setLevel(0);
// 空行,不處理
String trimStr = line.trim();
if (trimStr.isEmpty()) {
node.setEmptyLine(Boolean.TRUE);
return node;
}
// 行注釋,不處理
if (trimStr.startsWith("#")) {
node.setHeadRemark(trimStr.replaceFirst("#", "").trim());
return node;
}
// 處理值
String[] strs = line.split(":", 2);
// 拆分后長度為0的,屬于異常數(shù)據(jù),不做處理
if (strs.length == 0) {
return node;
}
// 獲取鍵
node.setKey(strs[0].trim());
// 獲取值
String value;
if (strs.length == 2) {
value = strs[1];
} else {
value = "";
}
// 獲取行末備注
String tailRemark = "";
if (value.contains(" #")) {
String[] vs = value.split("#", 2);
if (vs.length == 2) {
value = vs[0];
tailRemark = vs[1];
}
}
node.setTailRemark(tailRemark.trim());
node.setValue(value.trim());
// 獲取當前層級
int level = getNodeLevel(line);
node.setLevel(level);
node.setEffective(Boolean.TRUE);
return node;
}
private static int getNodeLevel(String line) {
if (line.trim().isEmpty()) {
return 0;
}
char[] chars = line.toCharArray();
int count = 0;
for (char c : chars) {
if (c != ' ') {
break;
}
count++;
}
return count / 2;
}
}
class YmlNode {
/** 層級關(guān)系 */
private Integer level;
/** 鍵 */
private String key;
/** 值 */
private String value;
/** 是否為空行 */
private Boolean emptyLine;
/** 當前行是否為有效配置 */
private Boolean effective;
/** 頭部注釋(單行注釋) */
private String headRemark;
/** 末尾注釋 */
private String tailRemark;
/** 是否為最后一層配置 */
private Boolean last;
public Boolean getLast() {
return last;
}
public void setLast(Boolean last) {
this.last = last;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Boolean getEmptyLine() {
return emptyLine;
}
public void setEmptyLine(Boolean emptyLine) {
this.emptyLine = emptyLine;
}
public Boolean getEffective() {
return effective;
}
public void setEffective(Boolean effective) {
this.effective = effective;
}
public String getHeadRemark() {
return headRemark;
}
public void setHeadRemark(String headRemark) {
this.headRemark = headRemark;
}
public String getTailRemark() {
return tailRemark;
}
public void setTailRemark(String tailRemark) {
this.tailRemark = tailRemark;
}
}
到此這篇關(guān)于Yml轉(zhuǎn)properties文件工具類YmlUtils(不用引任何插件和依賴)的文章就介紹到這了,更多相關(guān)Yml轉(zhuǎn)properties文件工具類YmlUtils內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于@SpringBootApplication與@SpringBootTest的區(qū)別及用法
這篇文章主要介紹了關(guān)于@SpringBootApplication與@SpringBootTest的區(qū)別及用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
httpclient ConnectionHolder連接池連接保持源碼解析
這篇文章主要為大家介紹了httpclient ConnectionHolder連接池連接保持源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11
Java web基礎(chǔ)學習之開發(fā)環(huán)境篇(詳解)
下面小編就為大家?guī)硪黄狫ava web基礎(chǔ)學習之開發(fā)環(huán)境篇(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
Spring AOP + 注解實現(xiàn)統(tǒng)一注解功能
本文我們通過Spring AOP和Java的自定義注解來實現(xiàn)日志的插入功能,非常不錯,具有一定的參考借鑒價值,需要的朋友一起看看吧2018-05-05
springcloud gateway如何實現(xiàn)路由和負載均衡
這篇文章主要介紹了springcloud gateway如何實現(xiàn)路由和負載均衡的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Spring中@Transactional注解關(guān)鍵屬性和用法小結(jié)
在Spring框架中,@Transactional 是一個注解,用于聲明事務(wù)性的方法,它提供了一種聲明式的事務(wù)管理方式,避免了在代碼中直接編寫事務(wù)管理相關(guān)的代碼,本文給大家介紹@Transactional 注解的一些關(guān)鍵屬性和用法,感興趣的朋友一起看看吧2023-12-12

