情景
snakeyaml支持yml配置文件转map 也支持map转成yml文件 但是有些业务情景下会存在: yml配置文件转成map后,对<key,value>形式的map的数据进行处理, 比如增加、删除配置,或者修改配置的值, 然后再生成新的yml配置文件,这时导致的问题就是以前的yml文件的注释在新的yml配置文件中都没有了
只听到从知秋君办公室传来知秋君的声音: 岁暮锄犁傍空室,呼儿登山收橡实。有谁来对上联或下联?
解决办法
- 一开始考虑查看snakeyaml的源码,尝试覆盖一些源码的类去解决,能力有限,没尝试成功。
- 只能想到对文件进行处理了,对一开始的yml文件中的注释进行缓存,再生成新的yml文件后再把缓存好的注释正确的放进去
- 问题:如何缓存,如何正确的放入新的yml中?
- 个人方法:
此代码由一叶知秋网-知秋君整理首先:检查需要引入snakeyaml的依赖 代码:分为三个类 YamlUtils -- 对yml文件操作的工具类 CommentUtils -- 继承YamlUtils,对注解的处理(缓存注解,放入新的yml文件中) Comment -- 注释对象
YamlUtils
public class YamlUtils { private static final Logger logger = LoggerFactory.getLogger(YamlUtils.class); public static Map<String, Object> yml2Map(String path) throws FileNotFoundException { FileInputStream fileInputStream = new FileInputStream(path); Yaml yaml = new Yaml(); Map<String, Object> ret = (Map<String, Object>) yaml.load(fileInputStream); return ret; } public static void map2Yml(Map<String, Object> map, String path) throws IOException { File file = new File(path); FileWriter fileWriter = new FileWriter(file); DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(options); yaml.dump(map, fileWriter); } }
Comment
此代码由一叶知秋网-知秋君整理public class Comment { /** * 替换前的 */ private String key; /** * 替换后的 */ private String replaceKey; /** * 位置(默认为1,一般替换前的行内容(key)都不一样):若存在替换前的行内容(key)一致,则需此参数 */ private Integer sort; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getReplaceKey() { return replaceKey; } public void setReplaceKey(String replaceKey) { this.replaceKey = replaceKey; } public Integer getSort() { return sort; } public void setSort(Integer sort) { this.sort = sort; } }
CommentUtils
class CommentUtils extends YamlUtils { private static volatile List<Comment> commentList; private final static String C = "#"; private final static Integer ONE = 1; private final static String ENTER = "\r\n"; private final static String CHARSET = "UTF-8"; /*public static void main(String[] args) { Map<String, Object> map = null; String path1 = "C:\\Users\\work\\Hzero\\hzero-generator\\src\\main\\resources\\application.yml"; String path2 = "C:\\Users\\work\\Hzero\\hzero-helper-parent\\hzero-helper-upgrade\\test.yml"; try { // 1 map = yml2Map(path1); buildComment(path1); // 2 map2Yml(map, path2); addComment(path2); } catch (IOException e) { e.printStackTrace(); } }*/ public static void buildComment(String path) { commentList = new ArrayList<>(); Map<String, Integer> keyCountMap = new HashMap<>(); try { List<String> lines = FileUtils.readLines(new File(path), CHARSET); for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); String tempStr = line.trim(); if (tempStr.startsWith(C)) { String nextLine = lines.get(i+1); Comment comment = new Comment(); comment.setKey(nextLine); comment.setReplaceKey(line + ENTER + nextLine); comment.setSort(keyCountMap.merge(nextLine, ONE, Integer::sum)); commentList.add(comment); } else { if (tempStr.contains(C)) { String key = (C + StringUtils.substringBefore(line, C)).trim().substring(1); Comment comment = new Comment(); comment.setKey(key); comment.setReplaceKey(line); comment.setSort(keyCountMap.merge(key, ONE, Integer::sum)); commentList.add(comment); } } } } catch (IOException e) { e.printStackTrace(); } } public static void addComment(String path) { try { String content = FileUtils.readFileToString(new File(path), CHARSET); for (Comment comment : commentList) { content = replacePosition(content, comment.getKey(), comment.getReplaceKey(), comment.getSort()); } FileUtils.writeStringToFile(new File(path), content, CHARSET); } catch (IOException e) { e.printStackTrace(); } } /** * 替换第position个匹配的目标子串 * @param str 源字符串。 * @param target 需要替换的目标子串。 * @param replacement 需要替换成的字符串。 * @return 将源字符串中出现的第n个target换成replacement后的字符串。 */ public static String replacePosition(String str, String target, String replacement, int position) { if (str == null || target == null || replacement == null) { throw new NullPointerException(); } if (position < 2) { return str.replaceFirst(target, replacement); } String result = str; for (int i = 1; i < position; i++) { result = replaceSecond(result, target, replacement); } return result; } /** * 替换第二个匹配的目标子串 * @param str 源字符串。 * @param target 需要替换的目标子串。 * @param replacement 需要替换成的字符串。 * @return 将源字符串中出现的第二个target换成replacement后的字符串。 */ public static String replaceSecond(String str, String target, String replacement){ if (str == null || target == null || replacement == null) { throw new NullPointerException(); } int index = str.indexOf(target); if (index == -1) { throw new RuntimeException("Not Found."); } index = str.indexOf(target, index + target.length()); if (index == -1) { throw new RuntimeException("Not Found."); } String str1 = str.substring(0, index); String str2 = str.substring(index); str2 = str2.replaceFirst(target, replacement); return str1 + str2; } }
代码及其注释都很简单明了,稍微解释下之前第三点提的问题
-
buildComment方法去缓存一开始的yml文件中的注释
key – 表示带有注释的那一行的实际内容
replaceKey – 要替换key的内容
sort – key的顺序
比如:# 服务端口 server: port: ${HGEN_SERVER_PORT:8265} management: # actuator监控端口 server: port: ${HGEN_MANAGEMENT_SERVER_PORT:8266} endpoints: web: exposure: include: '*' #这是注释
举例:
一:注释在头上(1)# 服务端口 server:
此时,
key为server:
replaceKey为
# 服务端口 server:
sort为默认值1,因为整个文件中key为
server:
的只有这一行
二:注释在头上(2)
# actuator监控端口 server:
此时,
key为server:
replaceKey为
# actuator监控端口 server:
sort为默认值1,因为整个文件中key为
server:
的只有这一行
(注意看空格,因为存在空格所以上面两个key是不一样的,如果存在一样的key时,后面的key的sort会按顺序累加)三:注释在后面
include: '*' #这是注释
此时,
key为include: '*'
replaceKey为
include: '*' #这是注释
sort为默认值1,因为整个文件中key为
include: '*'
的只有这一行
-
addComment方法中把生成好的yml文件读成String,再循环缓存好的注释Comment对象List,通过replacePosition方法把带注释的内容替换进去,再写成文件。replacePosition和replaceSecond方法在代码中都有详细的说明