GeoTools中的XML处理 — 如何将Object转换为XML文档。
文章目录
1. 前言
书接前文GeoTools源码解读 - XML处理 ,本文中我们将补齐前文中没有解释完的内容,尝试解释GeoTools中是如何实现Java Bean转换XML文档的。
2. 概述
前文已经解释过了,GTXML是基于SAX来实现的XML处理,但在双向转换过程中,两者还是存在些许的区别,这里我们再贴一下前文中的对比表格:
类别 | 关键类 | Binding | SAX | 备注 |
---|---|---|---|---|
XML >>> Java Bean | org.geotools.xsd.Parser |
parse | javax.xml.parsers.SAXParser |
在SAX事件响应中进行XML解析 |
Java Bean >>> XML | org.geotools.xsd.Encoder |
encode / getProperties / getProperty | javax.xml.transform.sax.TransformerHandler |
需要主动调用对应的SAX方法来写入XML文档内容 |
3. 用例准备
本次使用的测试用例与前文GeoTools源码解读 - XML处理 一致。
单元测试执行后的最终返回Java Bean为: net.opengis.wfs20.impl.FeatureCollectionTypeImpl
实例,而在经过GeoTools处理之后,得到XML文档为:
<?xml version="1.0" encoding="UTF-8"?>
<wfs:FeatureCollection xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cdf="http://www.opengis.net/cite/data" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" numberMatched="1" numberReturned="1" timeStamp="2022-02-27T05:05:32.212Z" xsi:schemaLocation="http://www.opengis.net/cite/data http://localhost:8080/geoserver/wfs?service=WFS&version=2.0.0&request=DescribeFeatureType&typeName=cdf%3AOther http://www.opengis.net/wfs/2.0 http://localhost:8080/geoserver/schemas/wfs/2.0/wfs.xsd http://www.opengis.net/gml/3.2 http://localhost:8080/geoserver/schemas/gml/3.2.1/gml.xsd">
<wfs:boundedBy>
<gml:Envelope>
<gml:lowerCorner>500050 500050</gml:lowerCorner>
<gml:upperCorner>500050 500050</gml:upperCorner>
</gml:Envelope>
</wfs:boundedBy>
<wfs:member>
<cdf:Other gml:id="Other.0">
<gml:boundedBy>
<gml:Envelope srsName="urn:ogc:def:crs:EPSG::32615" srsDimension="2">
<gml:lowerCorner>500000 500000</gml:lowerCorner>
<gml:upperCorner>500100 500100</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<cdf:string2>sometimes</cdf:string2>
</cdf:Other>
</wfs:member>
</wfs:FeatureCollection>
4. 实现原理
首先我们贴一张官方给出的示意图:GeoTools - XML Encoder实现示意图
示意图展示得已经很清晰了,门面入口Encoder
类在XSD的辅助下将Java Bean序列化为响应的XML文档。
经过断点调试,我们可以得到如下堆栈图:
4.1 内部类Encoder.EncodingEntry
正式开始分析上述堆栈前,我们先介绍下Encoder
类型内部定义的一个内部类EncodingEntry
,其结构很简单,但却时下面这些将要解释的方法实现中会经常出现的客人以及重要逻辑关联单元。
以下就是EncodingEntry
的完整定义:
/** Encoding stack entries. */
private static class EncodingEntry {
// 本次将要被序列化为xml 的 java bean
public Object object;
// 从xsd文件中解析出的xml类型定义 - 与上面的java bean对应
public XSDElementDeclaration element;
// 本次java bean 对应的 xml node
public Element encoding;
//
public List<Object[]> children; // list of (element,iterator) tuples
//
public EncodingEntry parent;
public EncodingEntry(Object object, XSDElementDeclaration element, EncodingEntry parent) {
this.object = object;
this.element = element;
this.parent = parent;
children = new ArrayList<>();
}
}
相关的解释在上面的代码中已经备注了,记住它们之间的关系,有助于理解下面这些递归执行代码的逻辑。
4.2 关键执行逻辑
4.2.1 Encoder
相关
按照上述堆栈,对于Encoder
类型中的关键方法:
// ====================================================
// Encoder.java 上述堆栈图中的1号标识
public void encode(Object object, QName name, ContentHandler handler)
throws IOException, SAXException {
// 入参1: 就是本次需要序列化的java bean, 本示例中为 net.opengis.wfs20.impl.FeatureCollectionTypeImpl 示例
// 入参2: 为: new QName("http://www.opengis.net/wfs/2.0", "FeatureCollection")
// 入参3: 为: org.apache.xalan.transformer.TransformerIdentityImpl。 所以这次虽然方法参数要求的都是ContentHandler接口类型, 但这次不像前文中使用ParserHandler自定义类型.
// maintain a stack of (encoding,element declaration pairs)
Stack<EncodingEntry> encoded = null;
......
serializer = handler;
if (!inline) {
// SAX模式, 开启Document写入模式
serializer.startDocument();
}
......
// 维护一个全局Stack, 子元素为 Encoder.EncodingEntry
encoded = new Stack<>();
// 从xsd中解析出根节点所对应的xml节点定义. 本例中 name 为 new QName("http://www.opengis.net/wfs/2.0", "FeatureCollection")
XSDElementDeclaration root = getRootDeclaration(name);
// object就是本次需要从object转换为xml的 FeatureCollection 实例
encoded.add(new EncodingEntry(object, root, null));
// 开启递归调用, 执行序列化
while (!encoded.isEmpty()) {
EncodingEntry entry = encoded.peek();
if (entry.encoding != null) {
// element has been started, get the next child
if (!entry.children.isEmpty()) {
// 进行子元素的处理, 没有实际的序列化操作, 主要是进行上面提到的 encoded堆栈维护. (关于堆栈的变动, 参见官方文档: https://docs.geotools.org/latest/userguide/library/xml/internal/overview.html )
processChildren(handler, encoded, entry);
} else {
// no more children, finish the element
// 结束当前节点的序列化, 主要操作 endElement
finishElement(encoded, entry);
}
} else {
// 将当前节点序列化, 主要操作是 startElement, characters
// start the encoding of the entry
startEncoding(object, entry);
}
}
if (!inline) {
// SAX模式, 关闭Document
serializer.endDocument();
}
}
// ===================================================
// Encoder.java 上述堆栈图中的2号标识
private void startEncoding(Object object, EncodingEntry entry)
throws SAXException, IOException {
......
entry.encoding =
entry.parent != null
? (Element)
encode(entry.object, entry.element, entry.parent.element.getType())
: (Element) encode(entry.object, entry.element);
// 赋值填充给的是xml node, 而非java bean. 方法中会借助xsd检索出当前xml节点所应该拥有的attr.
// 这里注意的是, 与之前解释的xml -> object时候不同,本方法中, GeoTools将使用GetPropertyExecutor从object中获取attr对应的值, 以组装出xml node所需的element或attr
// add any more attributes
setupEntryAttributes(object, entry);
setupSchemaLocations(entry); // 赋值填充给的是xml node, 而非java bean
start(entry.encoding, entry.element); // 进行当前节点的xml文档写入, 除了自身外, 还有其下的childNode
populateChildren(entry); // 迭代处理子节点
}
// ===================================================
// Encoder.java 上述堆栈图中的3号标识
protected Node encode(Object object, XSDNamedComponent component, XSDTypeDefinition container) {
// encoder为Encoder类型内部的全局字段, 类型为ElementEncoder
if (component instanceof XSDElementDeclaration) {
// 解析element
XSDElementDeclaration element = (XSDElementDeclaration) component;
return encoder.encode(object, element, doc, container);
} else if (component instanceof XSDAttributeDeclaration) {
// 解析attr
XSDAttributeDeclaration attribute = (XSDAttributeDeclaration) component;
return encoder.encode(object, attribute, doc, container);
}
return null;
}
4.2.2 ElementEncoder
相关
在上述堆栈图中,序号3处所涉及的全局字段encoder
,其实际类型为ElementEncoder
:
- 该类型没有任何的继承关系,所以它只是一个辅助工具类。
- 本类中定义了两个
encode
方法。分别负责按照xsd定义转换出element,attr。而且在各自encode
方法中使用的ElementEncodeExecutor
,AttributeEncodeExecutor
实际逻辑处理类,它们的构造函数中就会创建出与当前java object匹配的 element或attr。而这些element或attr,正是在ElementEncodeExecutor
或AttributeEncodeExecutor
调用binding时传递给binding的最后一个参数,即与java bean对应的 xml node。
对于初次接触的读者来说,这段话有点晦涩,所以以下我们贴两张执行堆栈图
5. 总结
与前文类似, GeoTools依然是使用SAX模式来实现将Java Bean转换为XML文档,通过执行时维护的一个Stack<EncodingEntry>
,实现了递归转换操作。
最后仿照前文,给出一份最简示例代码——将Java Bean转换为响应的XML文档:
// instantiate the configuration for the filter schema
final org.geotools.wfs.v2_0.WFSConfiguration configuration = new org.geotools.wfs.v2_0.WFSConfiguration();
// create the encoder
final Encoder encoder = new Encoder(configuration);
// get a FeatureCollectionType
net.opengis.wfs20.FeatureCollectionType d = Wfs20Factory.eINSTANCE.createFeatureCollectionType();
d.setNumberMatched(1);
d.setNumberReturned(BigInteger.valueOf(2L));
// get the name of the 'FeatureCollection' element in the schema
QName name = new QName("http://www.opengis.net/wfs/2.0", "FeatureCollection");
// encode
String encodeAsString = encoder.encodeAsString(d, name);
Console.log(XmlUtil.format(encodeAsString));