写点什么

netty 系列之:netty 中常用的 xml 编码解码器

作者:程序那些事
  • 2022 年 4 月 22 日
  • 本文字数:2155 字

    阅读完需:约 7 分钟

netty系列之:netty中常用的xml编码解码器

简介

在 json 之前,xml 是最常用的数据传输格式,虽然 xml 的冗余数据有点多,但是 xml 的结构简单清晰,至今仍然运用在程序中的不同地方,对于 netty 来说自然也提供了对于 xml 数据的支持。


netty 对 xml 的支持表现在两个方面,第一个方面是将编码过后的多个 xml 数据进行 frame 拆分,每个 frame 包含一个完整的 xml。另一方面是将分割好的 frame 进行 xml 的语义解析。


进行 frame 拆分可以使用 XmlFrameDecoder,进行 xml 文件内容的解析则可以使用 XmlDecoder,接下来我们会详细讲解两个 decoder 实现和使用。

XmlFrameDecoder

因为我们收到的是数据流,所以不确定收到的数据到底是什么样的,一个正常的 xml 数据可能会被拆分成多个数据 frame。


如下所示:


   +-------+-----+--------------+   | <this | IsA | XMLElement/> |   +-------+-----+--------------+
复制代码


这是一个正常的 xml 数据,但是被拆分成为了三个 frame,所以我们需要将其合并成为一个 frame 如下:


   +-----------------+   | <thisIsAXMLElement/> |   +-----------------+
复制代码


还有可能不同的 xml 数据被分拆在多个 frame 中的情况,如下所示:


   +-----+-----+-----------+-----+----------------------------------+   | <an | Xml | Element/> | <ro | ot><child>content</child></root> |   +-----+-----+-----------+-----+----------------------------------+
复制代码


上面的数据需要拆分成为两个 frame:


   +-----------------+-------------------------------------+   | <anXmlElement/> | <root><child>content</child></root> |   +-----------------+-------------------------------------+
复制代码


拆分的逻辑很简单,主要是通过判断 xml 的分隔符的位置来判断 xml 是否开始或者结束。xml 中的分隔符有三个,分别是'<', '>' 和 '/'。


在 decode 方法中只需要判断这三个分隔符即可。


另外还有一些额外的判断逻辑,比如是否是有效的 xml 开始字符:


    private static boolean isValidStartCharForXmlElement(final byte b) {        return b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' || b == ':' || b == '_';    }
复制代码


是否是注释:


    private static boolean isCommentBlockStart(final ByteBuf in, final int i) {        return i < in.writerIndex() - 3                && in.getByte(i + 2) == '-'                && in.getByte(i + 3) == '-';    }
复制代码


是否是 CDATA 数据:


    private static boolean isCDATABlockStart(final ByteBuf in, final int i) {        return i < in.writerIndex() - 8                && in.getByte(i + 2) == '['                && in.getByte(i + 3) == 'C'                && in.getByte(i + 4) == 'D'                && in.getByte(i + 5) == 'A'                && in.getByte(i + 6) == 'T'                && in.getByte(i + 7) == 'A'                && in.getByte(i + 8) == '[';
复制代码


通过使用这些方法判断好 xml 数据的起始位置之后,就可以调用 extractFrame 方法将要使用的 ByteBuf 从原始数据中拷贝出来,最后放到 out 中去:


final ByteBuf frame =                    extractFrame(in, readerIndex + leadingWhiteSpaceCount, xmlElementLength - leadingWhiteSpaceCount);            in.skipBytes(xmlElementLength);            out.add(frame);
复制代码

XmlDecoder

将 xml 数据拆分成为一个个 frame 之后,接下来就是对 xml 中具体数据的解析了。


netty 提供了一个 xml 数据解析的方法叫做 XmlDecoder,主要用来对已经是一个单独的 xml 数据的 frame 进行实质内容的解析,它的定义如下:


public class XmlDecoder extends ByteToMessageDecoder 
复制代码


XmlDecoder 根据读取到的 xml 内容,将 xml 的部分拆分为 XmlElementStart,XmlAttribute,XmlNamespace,XmlElementEnd,XmlProcessingInstruction,XmlCharacters,XmlComment,XmlSpace,XmlDocumentStart,XmlEntityReference,XmlDTD 和 XmlCdata。


这些数据基本上覆盖了 xml 中所有可能出现的元素。


所有的这些元素都是定义在 io.netty.handler.codec.xml 包中的。


但是 XmlDecoder 对 xml 的读取解析则是借用了第三方 xml 工具包:fasterxml。


XmlDecoder 使用了 fasterxml 中的 AsyncXMLStreamReader 和 AsyncByteArrayFeeder 用来进行 xml 数据的解析。


这两个属性的定义如下:


    private static final AsyncXMLInputFactory XML_INPUT_FACTORY = new InputFactoryImpl();    private final AsyncXMLStreamReader<AsyncByteArrayFeeder> streamReader;    private final AsyncByteArrayFeeder streamFeeder;
this.streamReader = XML_INPUT_FACTORY.createAsyncForByteArray(); this.streamFeeder = (AsyncByteArrayFeeder)this.streamReader.getInputFeeder();
复制代码


decode 的逻辑是通过判断 xml element 的类型来分别进行不同数据的读取,最后将读取到的数据封装成上面我们提到的各种 xml 对象,最后将 xml 对象添加到 out list 中返回。

总结

我们可以借助 XmlFrameDecoder 和 XmlDecoder 来实现非常方便的 xml 数据解析,netty 已经为我们造好轮子了,我们就不需要再自行发明了。


本文已收录于 http://www.flydean.com/14-7-netty-codec-xml/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 刚刚阅读数: 3
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:netty中常用的xml编码解码器_Java_程序那些事_InfoQ写作社区