• SnakeYaml快速入门


    转自:https://www.jianshu.com/p/d8136c913e52

    【原创文章,转载请注明原文章地址,谢谢!】

    在YAML快速入门[https://www.jianshu.com/p/97222440cd08]中,我们已经简单介绍了YAML的语法,本节中主要介绍YAML的配置读取。

    目前有很多可以生成和解析YAML的第三方工具,常见的,如SnakeYaml,jYaml,Jackson等,但是不同的工具功能还是差距较大,比如jYaml就不支持合并(<<)和(---)操作。我们下面来看看Springboot使用的SnakeYaml的基本使用方式。

    简介

    SnakeYaml是一个完整的YAML1.1规范Processor,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型(map,omap,set,常量,具体参考http://yaml.org/type/index.html)。

    快速使用

    要使用SnakeYaml,首先引入maven依赖:

    <dependency>
        <groupId>org.yaml</groupId>
        <artifactId>snakeyaml</artifactId>
        <version>1.17</version>
    </dependency>
    

    我们来完成一个最简单的yaml解析例子:

    @Test
    public void testLoad() {
        String yamlStr = "key: hello yaml";
        Yaml yaml = new Yaml();
        Object ret = yaml.load(yamlStr);
        System.out.println(ret);
    }
    

    结果输出:

    {key=hello yaml}
    

    简介解释:
    1,使用Yaml类,创建一个Yaml对象,所有的解析操作都是从这个对象开始;
    2,声明了一个yaml的字符串(当然也可以使用yaml文档等),定义了一个对象:key: hello yaml;
    3,使用Yaml对象的load方法 public Object load(String yaml)加载一段yaml字符串,返回解析之后的对象;
    我们通过打印ret的类型:

    System.out.println(ret.getClass().getSimpleName());
    

    可以看到,实际创建的是一个Map:LinkedHashMap。

    load/loadAll/loadAs 方法使用

    Yaml的load方法可以传入多种参数类型:
    public Object load(String yaml)
    public Object load(InputStream io)
    public Object load(Reader io)

    三个方法都是通过传入的不同类型的内容,解析得到结果对象。需要注意一点的是,SnakeYaml通过读入的内容是否包含BOM头来判断输入流的编码格式。如果不包含BOM头,默认为UTF-8编码。

    下面再来看一个解析案例,这次使用yaml文件的方式。首先创建一个yaml文件:

    #test.yaml
    - value1
    - value2
    - value3
    

    很明显结果应该是一个List集合,把该文件放到resources下:

    打印结果:

    [value1, value2, value3]
    

    如果需要加载的yaml文件中包含多个yaml片段,则可以使用loadAll方法加载所有的yaml内容。比如有如下一个yaml文件:

    #test2.yaml
    sample1: 
        r: 10
    sample2:
        other: haha
    sample3:
        x: 100
        y: 100
    

    这个yaml文件内容很明显是一个对象(或者map),对象的每一个属性对应的又是一个对象。要加载这个yaml文件,代码应该是:

    打印结果:

    {sample1={r=10}, sample2={other=haha}, sample3={x=100, y=100}}
    

    如果我们稍微修改一下test2.yaml文件:

    ---
    sample1: 
        r: 10
    ---
    sample2:
        other: haha
    --- 
    sample3:
        x: 100
        y: 100
    

    按照YAML规范,这应该是三个yaml配置片段了。那么如果再使用上面的代码解析,就会报错:

    image.png

    可以看到,load方法无法处理---标记。

    这种时候只能使用loadAll方法解析:
    public Iterable<Object> loadAll(String yaml)
    public Iterable<Object> loadAll(InputStream yaml)
    public Iterable<Object> loadAll(Reader yaml)

    可以看到,loadAll方法返回的是一个Object的迭代对象,那么其中的每一个Object就是每一个yaml片段解析出来的对象:

    打印的结果为:

    {sample1={r=10}}
    {sample2={other=haha}}
    {sample3={x=100, y=100}}
    

    可以看到,test2.yaml被解析成了三个Map。
    这里需要注意一点的是,SnakeYaml是在每一次遍历的时候(即调用Iteratable的forEach方法的时候),才会去解析下一个---分割的yaml片段。

    上面所有的实例,都是把yaml配置转化成Map或者Collection,如果我们想直接把yaml配置转成指定对象呢?下面我们通过三个示例来简单看一下:

    #address.yaml
    lines: |
      458 Walkman Dr.
      Suite #292
    city: Royal Oak
    state: MI
    postal: 48046
    

    有指定的Address模型,我们想把address.yaml内容直接转化成Address对象:

    @Setter
    @Getter
    @ToString
    public class Address {
        private String lines;
        private String city;
        private String state;
        private Integer postal;
    }
    

    只需要使用Yaml的loadAs方法即可:

    @Test
    public void testAddress() throws Exception {
        Yaml yaml = new Yaml();
        Address ret = yaml.loadAs(this.getClass().getClassLoader()
                .getResourceAsStream("address.yaml"), Address.class);
        Assert.assertNotNull(ret);
        Assert.assertEquals("MI", ret.getState());
    }
    

    loadAs方法的第二个参数类型,即是要创建的用于包装yaml数据的类型。
    这是第一种方式,对于常见的对象包装其实已经完全足够,我们来看下第二种方式,第二种方式也比较简单,即使用YAML的!!类型强转来完成。这次的类型再复杂一些,我们创建一个Person类型:

    @Setter
    @Getter
    @ToString
    public class Person {
    
        private String given;
        private String family;
        private Address address;
    }
    

    这个Person类型包含了我们上一个示例中的Address类型,来添加一个yaml文件:

    #person.yaml
    --- !!cn.wolfcode.yaml.demo.domain.Person
    given  : Chris
    family : Dumars
    address:
        lines: |
            458 Walkman Dr.
            Suite #292
        city    : Royal Oak
        state   : MI
        postal  : 48046
    

    注意第一行,我们使用---代表一个yaml文档的开始,并且立刻使用!!告诉下面的类型为cn.wolfcode.yaml.demo.domain.Person。这样配置之后,我们就可以直接使用load方法来加载对象了:

    @Test
    public void testPerson() throws Exception {
        Yaml yaml = new Yaml();
        Person ret = (Person) yaml.load(this.getClass().getClassLoader()
                .getResourceAsStream("person.yaml"));
        Assert.assertNotNull(ret);
        Assert.assertEquals("MI", ret.getAddress().getState());
    }
    

    我们直接使用load方法加载对象,并直接转化成Person对象即可。

    第三种方式,其实是第一种loadAs方法的实现原理,即在创建Yaml对象时,配置用于映射文档的root构造器。首先去掉person.yaml第一行配置:

    #person.yaml
    given  : Chris
    family : Dumars
    address:
        lines: |
            458 Walkman Dr.
            Suite #292
        city    : Royal Oak
        state   : MI
        postal  : 48046
    

    实现代码:

    @Test
    public void testPerson2() {
        Yaml yaml = new Yaml(new Constructor(Person.class));
        Person ret = (Person) yaml.load(this.getClass().getClassLoader()
                .getResourceAsStream("person.yaml"));
        Assert.assertNotNull(ret);
        Assert.assertEquals("MI", ret.getAddress().getState());
    }
    

    可以看到,我们在创建Yaml对象的时候,传入了一个new Constructor(Person.class)对象,即指定了,yaml文件的root对象使用Person类型。注意这个Constructor是org.yaml.snakeyaml.constructor.Constructor对象。

    SnakeYaml还能正确的识别集合中的类型。我们修改Person类:

    @Setter
    @Getter
    @ToString
    public class Person {
    
        private String given;
        private String family;
        private List<Address> address;
    }
    

    在这里,address属性变成了一个类型安全的List,修改我们的person.yaml文件:

    --- !!cn.wolfcode.yaml.demo.domain.Person
    given  : Chris
    family : Dumars
    address:
        - 
          lines: 458 Walkman
          city    : Royal Oak
          state   : MI
          postal  : 48046
        - 
          lines: 459 Walkman
          city    : Royal Oak
          state   : MI
          postal  : 48046
    

    我们的address属性由两个address构成,我们来看下这种情况下,是否能正确的识别:

    @Test
    public void testTypeDesc(){
        Yaml yaml = new Yaml(new Constructor(Person.class));
        Person ret = (Person) yaml.load(this.getClass().getClassLoader()
                .getResourceAsStream("person.yaml"));
        System.out.println(ret);
    }
    

    我们来看下输出:
    Person(given=Chris, family=Dumars, address=[Address(lines=458 Walkman, city=Royal Oak, state=MI, postal=48046), Address(lines=459 Walkman, city=Royal Oak, state=MI, postal=48046)])
    可以看到,确实正确的识别到了address集合中的Address类型。

    如果要明确数据类型,可以使用TypeDescription来描述具体的数据类型:

    @Test
    public void testTypeDesc() {
        Constructor cons = new Constructor(Person.class);
        TypeDescription td = new TypeDescription(Person.class);
        td.putListPropertyType("address", Address.class);
        cons.addTypeDescription(td);
        
        Yaml yaml = new Yaml();
        Person ret = (Person) yaml.load(this.getClass().getClassLoader()
                .getResourceAsStream("person.yaml"));
        System.out.println(ret);
    }
    

    可以看到,首先创建了一个Person类型的构造器用于映射yaml文档根类型,接着创建了一个TypeDescription,并传入Person类型,代表这个TypeDescription是用来描述Person类型的结构,然后通过putListPropertyType(propertName,propertyType)来指定Person类型的address属性集合中的类型为Address类型,最后将这个类型描述注册到构造器描述中。
    TypeDescription类型最常用的两个方法分别是:

    public void putListPropertyType(String property, Class<? extends Object> type)
    public void putMapPropertyType(String property, Class<? extends Object> key,
            Class<? extends Object> value)
    

    分别用于限制List集合属性类型和Map集合属性类型,当然,Map类型需要分别指定key和value的值类型。

    dump入门

    上面简单的介绍了snakeYaml用于yaml文件的解析,下面简单通过几个例子看看怎么使用snakeYaml生成yaml文件。当然,对于yaml来说,更多的时候是作为配置文件存在。

    首先我们来看一个简单的生成yaml格式字符串的例子:

    结果输出:

    {key1: value1, key2: 123}
    

    代码非常简单,直接使用Yaml的dump方法,就可以把一个对象输出到一个Writer中。我们简单的看一下dump方法的重载:

    image.png

    非常明确,dump用于输出一个对象,而dumpAll和loadAll方法对应,可以输出一组对象。

    下面我们来测试一个自定义对象的输出:

    @Test
    public void testDump2() {
        Address adr = new Address();
        adr.setCity("Royal Oak");
        adr.setLines("458 Walkman");
        adr.setPostal(48046);
        adr.setState("MI");
    
        Yaml yaml = new Yaml();
        StringWriter sw = new StringWriter();
        yaml.dump(adr, sw);
        System.out.println(sw.toString());
    }
    

    输出结果为:
    !!cn.wolfcode.yaml.demo.domain.Address {city: Royal Oak, lines: 458 Walkman, postal: 48046,
    state: MI}

    接下来再来演示一个输出多个对象的情况:

    @Test
    public void testDump3() {
        Address adr = new Address();
        adr.setCity("Royal Oak");
        adr.setLines("458 Walkman");
        adr.setPostal(48046);
        adr.setState("MI");
        
        Address adr2 = new Address();
        adr2.setCity("Royal Oak");
        adr2.setLines("459 Walkman");
        adr2.setPostal(48046);
        adr2.setState("MI");
        
        List<Address> target=new ArrayList<>();
        target.add(adr);
        target.add(adr2);
    
        Yaml yaml = new Yaml();
        StringWriter sw = new StringWriter();
        yaml.dumpAll(target.iterator(), sw);
        System.out.println(sw.toString());
    }
    

    输出结果为:

    !!cn.wolfcode.yaml.demo.domain.Address {city: Royal Oak, lines: 458 Walkman, postal: 48046,
    state: MI}
    --- !!cn.wolfcode.yaml.demo.domain.Address {city: Royal Oak, lines: 459 Walkman, postal: 48046,
    state: MI}
    符合预期。

    当然,关于dump方法的更多使用,比如设置生成样式的DumperOptions,设置Tag格式的Representer等更高级一些的需求,大家可以查看SnakeYaml的开发文档:https://bitbucket.org/asomov/snakeyaml/wiki/Documentation

  • 相关阅读:
    设计模式之策略模式
    UML类图几种关系的总结
    LinuxMint下安装使用Umbrello(UML工具)
    Linux环境变量
    随笔
    Unity Animation Scripting zz
    FSM:游戏开发中的有限状态机(理论篇)转
    统计帧率的几种方法
    图形学 游戏 学习链接汇总
    福尔摩斯女友
  • 原文地址:https://www.cnblogs.com/digod/p/12572265.html
Copyright © 2020-2023  润新知