Yaml及解析框架SnakeYaml简介及TypeDescription的使用和原理

Yaml及解析框架SnakeYaml简介及TypeDescription的使用和原理

YAML简介

YAML(YAML Ain't Markup Language,即YAML不是一种标记语言),简称yml,是一种直观的数据序列化格式,广泛用于配置文件,同时也被用于数据交换和存储。YAML的设计目标是易于人类阅读和编写,同时也易于机器解析和生成。它基于Unicode,并且支持多种编程语言。

YAML的主要特点

1)易于阅读:YAML使用缩进表示数据层次结构,使得它看起来很像自然语言的文档结构,易于人类理解和编写;

2)简洁性:YAML的数据结构通过少量的符号来表示,比如冒号“:”用于键值对,破折号“-”用于列表项,缩进表示层级。使得YAML文件通常比XML或JSON文件更简洁;

3)扩展性:YAML支持自定义数据类型,它可以在不修改核心语法的情况下扩展其用途;

4)跨语言支持:YAML被多种编程语言支持,包括Python、Ruby、Perl、Java等,在不同系统和应用之间交换数据变得容易;

5)注释:YAML支持注释,有助于说明文档的结构和内容,使得维护变得更加容易;

使用#符号开始,#后面的所有内容都将被视为注释,直到该行介绍。注释可以出现在行的开始、中间或末尾

SnakeYaml简介

SnakeYAML是用于Java的YAML解析器和发射器,是一个用于解析YAML、序列化以及反序列化的第三方框架。

1)解析YAML:SnakeYaml能够解析YAML格式的数据,将其转换为Java对象或Java中可操作的数据结构(如Map、List等);

2)序列化/反序列化Java对象:SnakeYAML支持将Java对象序列化为YAML格式的字符串,便于存储或传输,同时支持将YAML格式的字符串系列化为Java对象;

使用SnakeYaml解析Yaml的实现

以下以Dog、Cat的yaml文件解析为例,分享一下SnakeYaml解析Yaml的使用。

4.1 snakeyaml依赖添加

org.yaml

snakeyaml

1.33

4.2 实体类

public interface Animal {

}

import lombok.Getter;

import lombok.Setter;

import lombok.ToString;

@Setter

@Getter

@ToString

public class Cat implements Animal {

private String color;

private int age;

}

import lombok.Getter;

import lombok.Setter;

import lombok.ToString;

@Setter

@Getter

@ToString

public class Dog implements Animal {

private String color;

private int age;

}

定义一个接口Animal,Dog和Cat实现了Animal接口。

4.3 yaml配置类

import lombok.Getter;

import lombok.Setter;

@Getter

@Setter

public class YamlTest1Configuration {

private Dog dog;

private Cat cat;

}

配置类中包含Dog和Cat对象。

4.4 test1.yml配置文件

dog:

color: black

age: 2

cat:

color: white

age: 1

4.5 yaml系列化为Java配置类

import org.yaml.snakeyaml.Yaml;

import java.io.InputStream;

public class Test1 {

public static void main(String[] args) {

Yaml yaml = new Yaml();

InputStream resource = Test1.class.getClassLoader().getResourceAsStream("test1.yml");

YamlTest1Configuration yamlTest1Configuration = yaml.loadAs(resource, YamlTest1Configuration.class);

System.out.println("cat : " + yamlTest1Configuration.getCat().toString());

System.out.println("dog : " + yamlTest1Configuration.getDog().toString());

}

}

4.6 输出结果

以上为SnakeYaml解析为Java对象的简单使用,其中YamlTest1Configuration的格式同yaml文件的配置格式一一对应,使用较为简单。除了以上使用以外,SnakeYaml还支持更加复杂的yaml文件解析,以下介绍一下SnakeYaml自定义标签的使用。

SnakeYaml自定义标签的实现及解析

5.1 简介

Yaml中直接定义标签(以 ! 符号自定义标签)并不是Yaml规范的一部分,而是由处理SnakeYaml来解释。在SnakeYaml框架中,可以在Java代码中定义标签,并通过Representer和Constructor类来告诉SnakeYaml如何序列化和反序列化带有这些标签的YAML节点。

1)Representer:用于定义如何将Java对象转换为Yaml表示。通过Representer注册一个Representer实例,并为其指定一个自定义标签;

2)Constructor:用于定义如何从Yaml节点创建Java对象。通过注册一个Constructor实例,再通过TypeDescription指定自定义标签及对应Java对象;

5.2 实现

使用的依赖和实体类同上面的示例。

5.2.1 yaml配置类

import lombok.Getter;

import lombok.Setter;

import java.util.List;

@Setter

@Getter

public class YamlTest2Configuration {

private List animals;

}

此处的Java对象只解析获取Animal集合。

5.2.2 test2.yml配置文件

animals:

- !Dog # Dog标签

color: black

age: 2

- !Cat # Cat标签

color: white

age: 1

在配置文件中,通过 ! 符号,定义了Dog和Cat标签,用于指定标签下面的信息为对应的标签内容。

5.2.3 yaml系列化为Java配置类

import org.yaml.snakeyaml.TypeDescription;

import org.yaml.snakeyaml.Yaml;

import org.yaml.snakeyaml.constructor.Constructor;

import java.io.InputStream;

import java.util.List;

public class Test2 {

public static void main(String[] args) {

// 定义一个Constructor构造器

Constructor constructor = new Constructor(YamlTest2Configuration.class);

// 定义一个TypeDescription,指定Cat类对应!Cat标签

TypeDescription typeDescription = new TypeDescription(Cat.class, "!Cat");

// 添加到构造器中

constructor.addTypeDescription(typeDescription);

constructor.addTypeDescription(new TypeDescription(Dog.class, "!Dog"));

Yaml yaml = new Yaml(constructor);

InputStream resource = Test2.class.getClassLoader().getResourceAsStream("test2.yml");

YamlTest2Configuration yamlTest2Configuration = yaml.loadAs(resource, YamlTest2Configuration.class);

List animals = yamlTest2Configuration.getAnimals();

for (Animal a : animals) {

System.out.println(a.toString());

}

}

}

5.2.4 输出结果

SnakeYaml解析的原理

6.1 Yaml字符串信息解析成Node,每个Node包含Tag(标签)、value、起始位置、type等。每个键值对应一个NodeTuple,该对象包含keyNode和valueNode,分别表示键和值;

Node节点包含:

1)ScalarNode:标量节点。为叶子节点,即为确定的常量值。如keyNode,只能是字符串,一定为ScalarNode,其value为String类型;

2)CollectionNode:集合节点,抽象类,表示一组Node;

3)SequenceNode:有序集合节点,继承CollectionNode。对应的配置值为List集合的,其value为List

4)MappingNode:键值对集合节点,继承CollectionNode。对应一组key、value的配置,其value为List;

6.2 解析的核心方法为Constructor.construct(Node node),执行如下:

1)在BaseConstructor.newInstance(Class ancestor, Node node, boolean tryDefault)方法中,根据Node的type,从typeDescriptions集合中找到对应类型的TypeDescription对象;

type的设置主要以下两种:

a)在Constructor.constructJavaBean2ndStep()中通过通过Introspector(内省,类似反射技术)获得对象属性的Property,根据Property获取属性type,调用MappingNode.setTypes()或Node.setType()进行设置(解析时,Constructor.constructJavaBean2ndStep()方法层层递归调用);

b)BaseConstructor.constructObjectNoCheck(Node node)【获取节点的值】 -> Construct.construct() -> getConstructor() -> getClassForNode(),在该方法中,通过node.getTag()标签,从typeTags集合【通过constructor.addTypeDescription()添加TypeDescription时,会将type及标签添加到typeTags集合中】中获取标签对应的类,然后执行node.setType()设置节点的真实类型;

2)执行Constructor.ConstructMapping.constructJavaBean2ndStep(MappingNode node, Object object);

snakeymal-1.33版本的源码如下:

package org.yaml.snakeyaml.constructor;

public class Constructor extends SafeConstructor {

/**

* @param node:节点

* @param object:节点对应的对象。如YamlTest2Configuration对象

*/

protected Object constructJavaBean2ndStep(MappingNode node, Object object) {

// node.values的合并

flattenMapping(node, true);

// 如YamlTest2Configuration.class

Class beanType = node.getType();

List nodeValue = node.getValue();

// 遍历value

for (NodeTuple tuple : nodeValue) {

// value为SequenceNode,集合中的Node为MappingNode

Node valueNode = tuple.getValueNode();

// flattenMapping enforces keys to be Strings

// 获取key,如animals

String key = (String) constructObject(tuple.getKeyNode());

try {

// 获取node对应的TypeDescription

TypeDescription memberDescription = typeDefinitions.get(beanType);

// 获取key对应的Property

Property property = memberDescription == null ? getProperty(beanType, key)

: memberDescription.getProperty(key);

if (!property.isWritable()) {

throw new YAMLException(

"No writable property '" + key + "' on class: " + beanType.getName());

}

// 设置value的type。如animals的type为List

valueNode.setType(property.getType());

final boolean typeDetected =

memberDescription != null && memberDescription.setupPropertyType(key, valueNode);

if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) {

// only if there is no explicit TypeDescription

// 获取实际参数类型,如Animal

Class[] arguments = property.getActualTypeArguments();

if (arguments != null && arguments.length > 0) {

// type safe (generic) collection may contain the

// proper class

// 如:animals的valueNode为sequence,List集合参数的值

if (valueNode.getNodeId() == NodeId.sequence) {

// 获取第一个参数类型,如:Animal

Class t = arguments[0];

// 强转

SequenceNode snode = (SequenceNode) valueNode;

// 设置集合的泛型

snode.setListType(t);

} else if (Map.class.isAssignableFrom(valueNode.getType())) {

// 如果是Map参数的值,则对应的valueNode为MappingNode,分别获取key和value的实际类型

Class keyType = arguments[0];

Class valueType = arguments[1];

MappingNode mnode = (MappingNode) valueNode;

mnode.setTypes(keyType, valueType);

mnode.setUseClassConstructor(true);

} else if (Collection.class.isAssignableFrom(valueNode.getType())) {

// 如果是Collection集合,则对应的valueNode为MappingNode

Class t = arguments[0];

MappingNode mnode = (MappingNode) valueNode;

mnode.setOnlyKeyType(t);

mnode.setUseClassConstructor(true);

}

}

}

// 获取value的值

Object value =

(memberDescription != null) ? newInstance(memberDescription, key, valueNode)

: constructObject(valueNode);

// Correct when the property expects float but double was constructed

// 如果对应的value为普通类型,直接强制

if (property.getType() == Float.TYPE || property.getType() == Float.class) {

if (value instanceof Double) {

value = ((Double) value).floatValue();

}

}

// Correct when the property a String but the value is binary

if (property.getType() == String.class && Tag.BINARY.equals(valueNode.getTag())

&& value instanceof byte[]) {

value = new String((byte[]) value);

}

// 通过Property,执行set方法,为对象对应属性设置值

if (memberDescription == null || !memberDescription.setProperty(object, key, value)) {

property.set(object, value);

}

} catch (DuplicateKeyException e) {

throw e;

} catch (Exception e) {

throw new ConstructorException(

"Cannot create property=" + key + " for JavaBean=" + object, node.getStartMark(),

e.getMessage(), valueNode.getStartMark(), e);

}

}

return object;

}

}

2.1)遍历node的values集合,即NodeTuple节点元组;

2.2)获取节点的key,即NodeTuple.keyNode中获取;

2.3)获取node.type的TypeDescription;

2.4)执行TypeDescription.getProperty() -> discoverProperty() -> PropertyUtils.getProperty() -> getProperty() -> getPropertiesMap(),获取key对应的Property;

在该方法中,通过Introspector(内省,类似反射技术),解析传入的Class及其父类中包含的属性、方法及对应类型信息封装成Property对象。

Property对象可以方便的获取属性、方法的详细信息(如方法对应的参数个数、参数类型)

2.5)通过constructObject()获取配置的value信息(newInstance()也是调用的该方法),返回Map或String等对象;

在BaseConstructor.constructObjectNoCheck(Node node)方法中,执行constructor.construct(node),获取配置的Map对象信息

2.6)执行property.set(object, value),如animals,则Property为MethodProperty,使用反射,调用set方法,进行赋值;

小结

SnakeYaml解析Yaml时可以通过以下方式实现相对复杂的配置:

1)可以在yaml文件中自定义标签,在代码中通过TypeDescription设置自定义标签对应的Java类型;

2)可以通过自定义Construct,实现Object construct(Node node)接口,返回node对应的Java对象;

在使用自定义的Construct时,需要自定义Constructor【继承SnakeYaml的Constructor】,重写Construct getConstructor(Node node)方法,再改方法中返回自定义的Construct。

实现原理:

在BaseConstructor.constructObjectNoCheck(Node node)方法中,调用getConstructor(Node node)获取一个Construct【自定义了Constructor,所以此处是从自定义的Constructor的getConstructor(Node node)方法中获取自定义的Construct】,通过执行Construct.construct(Node node)获得node对应的对象。

以上为本篇分享的全部内容。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

📚 相关推荐

中国制茶工具之魅力
微信如何删除全部好友
《暗黑破坏神4》全要塞位置 要塞占领方法及boss打法
现在微粒贷利率最低是多少?
魔兽世界7.3恶魔皮革介绍
龙之谷狂战士刷图加点,龙之谷狂战士:升级攻略推荐加点建议