该例子是模仿 sharding-jdbc 中的自定义标签对 List 中不同的对象进行解析,例如:这个yml配置文件 test 集合按照不同的 tag 进行区分,我们需要将不同的 tag 解析为不同的类
test:
- !TYPE1
name: zhj
- !TYPE2
age: 18
这里我们使用了 snakeyaml 库的依赖,这也是 springboot 的默认依赖库
<dependency>
<groupId>org.yamlgroupId>
<artifactId>snakeyamlartifactId>
<version>1.33version>
dependency>
自定义构造器,添加对应的 TypeDescription 类型描述器,用于将对应的标签和类进行关联起来
/**
* 自定义构造器
*/
public class CustomConstruct extends Constructor {
public CustomConstruct(LoaderOptions options) {
super(options);
this.addTypeDescription(new TypeDescription(CustomType1.class, "!TYPE1"));
this.addTypeDescription(new TypeDescription(CustomType1.class, "!TYPE2"));
}
public CustomConstruct() {
this(new LoaderOptions());
}
}
自定义结构体
@Data
public class CustomType1 {
private String name;
}
@Data
public class CustomType2 {
private Integer age;
}
测试代码
public class YmlParser {
public static void main(String[] args) {
Constructor construct = new CustomConstruct();
Yaml yaml = new Yaml(construct);
//读取classpath下面的application.yml文件
Object obj = yaml.load(YmlParser.class.getClassLoader().getResourceAsStream("application.yml"));
System.out.println(obj);
}
}
原理解析:
我们自定义的 CustomConstructor 继承至 Constructor 所以会默认注册下面的构造器,由于 snakeyaml 库会将每一个节点 Node 解析为对应 Tag 类型的数据,数据下面都可以找到对应的构造器
public Constructor(TypeDescription theRoot, Collection<TypeDescription> moreTDs, LoaderOptions loadingConfig) {
//先调用父类注册对应node节点类型构造器,父类中也默认注册了许多 Tag 类型的构造器
super(loadingConfig);
if (theRoot == null) {
throw new NullPointerException("Root type must be provided.");
}
//如果节点的类型为null,默认使用 ConstructYamlObject,这个也是我们自定义标签处理的类
this.yamlConstructors.put(null, new ConstructYamlObject());
if (!Object.class.equals(theRoot.getType())) {
rootTag = new Tag(theRoot.getType());
}
yamlClassConstructors.put(NodeId.scalar, new ConstructScalar());
yamlClassConstructors.put(NodeId.mapping, new ConstructMapping());
yamlClassConstructors.put(NodeId.sequence, new ConstructSequence());
addTypeDescription(theRoot);
if (moreTDs != null) {
for (TypeDescription td : moreTDs) {
addTypeDescription(td);
}
}
}
这里我们可以通过 yaml.load() 方法定位到调用的是 constructor 类,也是我们上面自定义好的构造器类
private Object loadFromReader(StreamReader sreader, Class<?> type) {
//将对应的数据流按照类型进行解析为 Node 节点
Composer composer = new Composer(new ParserImpl(sreader, this.loadingConfig), this.resolver, this.loadingConfig);
this.constructor.setComposer(composer);
//根据需要class类型进行解析,默认根节点的类型是object
return this.constructor.getSingleData(type);
}
protected Object constructObjectNoCheck(Node node) {
//判断当前节点有没有解析过
if (recursiveObjects.contains(node)) {
throw new ConstructorException(null, null, "found unconstructable recursive node",
node.getStartMark());
}
recursiveObjects.add(node);
//根据node类型获取到对应的Construct,通过 yamlConstructors中获取对应的构造器
Construct constructor = getConstructor(node);
//判断是否已经解析了,如果没有解析直接调用构造器进行解析,通过debug我们可以知道自定义的tag解析时是使用 ConstructYamlObject
Object data = (constructedObjects.containsKey(node)) ? constructedObjects.get(node)
: constructor.construct(node);
finalizeConstruction(node, data);
//解析完成之后存入缓存中
constructedObjects.put(node, data);
//移出当前节点是否正在解析
recursiveObjects.remove(node);
if (node.isTwoStepsConstruction()) {
constructor.construct2ndStep(node, data);
}
return data;
}
通过debug到 ConstructYamlObject 其中内部代码又是通过 ConstructMapping 进行处理的,解析自定义的List中的元素呢,解析出的节点类型是 Object 对象类型如果走的是else分支,通过 constructJavaBean2ndStep 源码可以看到获取到对应节点的类型并且到 typeDefinitions 缓存中获取到具体的关联解析类,然后就可以根据对应的tag进行解析了
public Object construct(Node node) {
MappingNode mnode = (MappingNode)node;
//判断节点class类型是否是map
if (Map.class.isAssignableFrom(node.getType())) {
return node.isTwoStepsConstruction() ? Constructor.this.newMap(mnode) : Constructor.this.constructMapping(mnode);
//判断节点类型是否是Collection
} else if (Collection.class.isAssignableFrom(node.getType())) {
return node.isTwoStepsConstruction() ? Constructor.this.newSet(mnode) : Constructor.this.constructSet(mnode);
} else {
//判断节点类型是否是对象
Object obj = Constructor.this.newInstance(mnode);
if (obj != BaseConstructor.NOT_INSTANTIATED_OBJECT) {
return node.isTwoStepsConstruction() ? obj : this.constructJavaBean2ndStep(mnode, obj);
} else {
}
}
}
protected Object constructJavaBean2ndStep(MappingNode node, Object object) {
...省略代码
//获取到对应的key
String key = (String)Constructor.this.constructObject(tuple.getKeyNode());
try {
//通过对应的beanType类型获取到对应的 TypeDescription 关联对象
TypeDescription memberDescription = (TypeDescription)Constructor.this.typeDefinitions.get(beanType);
Property property = memberDescription == null ? this.getProperty(beanType, key) : memberDescription.getProperty(key);
if (!property.isWritable()) {
throw new YAMLException("No writable property '" + key + "' on class: " + beanType.getName());
}
}
}
上面的源码解析呢只是通过debug进行大致的跟踪,最后到具体的处理类,主要是了解了一下 Constructor和TypeDescription 的关系,了解到 Constructor 主要是用于操控整个解析和实例化的流程,而 TypeDescription 则是定义具体的标签和 class类之间的关系。