自定义Yaml标签解析

该例子是模仿 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类之间的关系。

你可能感兴趣的:(java,spring)