该例子是模仿 sharding-jdbc 中的自定义标签对 List 中不同的对象进行解析,例如:这个yml配置文件 test 集合按照不同的 tag 进行区分,我们需要将不同的 tag 解析为不同的类
test:- !TYPE1name: zhj- !TYPE2age: 18
这里我们使用了 snakeyaml 库的依赖,这也是 springboot 的默认依赖库
<dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.33</version>
</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类型进行解析,默认根节点的类型是objectreturn 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解析时是使用 ConstructYamlObjectObject 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类型是否是mapif (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) {...省略代码//获取到对应的keyString 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类之间的关系。