snakeyaml操作yml文件中注释的处理

情景

snakeyaml支持yml配置文件转map

也支持map转成yml文件

但是有些业务情景下会存在:

yml配置文件转成map后,对<key,value>形式的map的数据进行处理,

比如增加、删除配置,或者修改配置的值,

然后再生成新的yml配置文件,这时导致的问题就是以前的yml文件的注释在新的yml配置文件中都没有了

解决办法

  1. 一开始考虑查看snakeyaml的源码,尝试覆盖一些源码的类去解决,能力有限,没尝试成功。
  2. 只能想到对文件进行处理了,对一开始的yml文件中的注释进行缓存,再生成新的yml文件后再把缓存好的注释正确的放进去
  3. 问题:如何缓存,如何正确的放入新的yml中?
  4. 个人方法:
	首先:检查需要引入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;
    }

}

代码及其注释都很简单明了,稍微解释下之前第三点提的问题

  1. 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: '*'
    

    的只有这一行

  2. addComment方法中把生成好的yml文件读成String,再循环缓存好的注释Comment对象List,通过replacePosition方法把带注释的内容替换进去,再写成文件。replacePosition和replaceSecond方法在代码中都有详细的说明

Java本身没有直接读取YAML文件包括注释的API,但可以使用第三方库SnakeYAML实现。 以下是一个示例代码,使用SnakeYAML库读取YAML文件内容包括注释: ```java import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.Tag; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.LinkedHashMap; import java.util.Map; public class YamlReader { public static void main(String[] args) throws FileNotFoundException { Yaml yaml = new Yaml(new CommentedConstructor()); InputStream inputStream = new FileInputStream("config.yml"); Map<String, Object> obj = yaml.load(inputStream); System.out.println(obj); } } class CommentedConstructor extends Constructor { public CommentedConstructor() { this.yamlConstructors.put(null, new CommentedConstruct()); } private class CommentedConstruct extends Constructor.ConstructYamlMap { @Override public Object construct(Node node) { Object obj = super.construct(node); if (node instanceof org.yaml.snakeyaml.nodes.MappingNode) { org.yaml.snakeyaml.nodes.MappingNode mnode = (org.yaml.snakeyaml.nodes.MappingNode) node; Map<String, Object> map = (Map<String, Object>) obj; for (org.yaml.snakeyaml.nodes.NodeTuple tuple : mnode.getValue()) { org.yaml.snakeyaml.nodes.Node keyNode = tuple.getKeyNode(); if (keyNode instanceof org.yaml.snakeyaml.nodes.ScalarNode) { org.yaml.snakeyaml.nodes.ScalarNode scalarNode = (org.yaml.snakeyaml.nodes.ScalarNode) keyNode; String key = scalarNode.getValue(); map.put(key, tuple.getValueNode().getRawValue()); // 将注释保存到map if (tuple.getValueNode().getTag().equals(Tag.COMMENT)) { map.put("#" + key, tuple.getValueNode().getValue()); } } } } return obj; } } } ``` 这里使用了CommentedConstructor继承自SnakeYAML的Constructor类,重写了构造函数,并在其对节点进行了处理。使用时只需要将YAML文件的路径修改为自己的文件路径即可。 需要注意的是,这里的注释是以键值对的方式保存在Map的,键值为"#"+原键名。因此在获取键值对时需要注意。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值