• 转 用 AXIOM 促进 XML 处理


    转自:http://www.ibm.com/developerworks/cn/xml/x-axiom/

    AXIOM 还不是另一种对象模型。它有着明确的设计目标:大幅提升 Apache 下一代 SOAP 协议栈 Axis 2 的性能。结果造就了不同于其他对象模型的 AXIOM(也称为 OM),因为它突出了构造的轻型,并且 仅当需要的时候才建立。由于是轻型的,它尽可能地减轻对系统资源的压力,特别是 CPU 和内存。同时,延迟构造又允许在其他部分还没有完成的时候使用树的一部分。AXIOM 强大的延迟构建能力源于底层的 Streaming API for XML (StAX) 解析器。AXIOM 提供了所有这些特性,同时幕后的复杂性对用户是透明的。

    使用 XMLBench Document Model Benchmark 测试(请参阅 参考资料)的结果表明,AXIOM 的性能和现有的高性能对象模型相当。但是 AXIOM 的内存占用要好于现有多数依靠 SAX 和/或 DOM 输入输出的对象模型。因此对于 Web 服务引擎或内存受限制设备这样的 XML 处理器,AXIOM 是一种理想的选择,它可用于一般的 XML 处理,但是有一个对 SOAP 优化了的可选层。

    使用 AXIOM

    在典型的 SOAP 引擎中,数据可能以三种不同的方法表示:

    • 序列化形式,如 XML 或二进制 XML。
    • 内存中基于树的对象模型,如 DOM。
    • 专用于特定语言的对象,如 Plain Old Java Object (POJO)。

    比如一个 Web 服务的调用。传递给服务提供商的数据可能是用语言专用的对象,对于 Java 技术就是 POJO。调用过程的第一步是将这些对象中的信息项放入 SOAP 信封,构造一个 SOAP 消息。因为 SOAP 消息是 XML 文档,所以 Web 服务还必须将数据项转化成要求的 XML 格式。在内存中表示 XML Infoset 需要构造一个对象树,供对象模型(AXIOM)使用。

    从头创建 AXIOM

    创建内存对象层次结构的第一步是创建一个对象工厂:

    OMFactory factory= OMAbstractFactory.getOMFactory();

     

    AXIOM 允许很多不同的对象工厂实现,但链表是最常用的。一旦建立了工厂,就可以开始构造树了。

    比如下面的 XML 片段:


    清单 1.Line item 细节

     

    <po:line-item po:quantity="2"

       xmlns:po="http://openuri.org/easypo">

          <po:description>

             Burnham's Celestial Handbook, Vol 2

          </po:description>

             <po:price>19.89</po:price>

    </po:line-item>

     

    注意,所有的元素和属性都属于 "http://openuri.org/easypo" 名称空间。因此,为这个 XML 片段构造 AXIOM 树的第一步就是创建名称空间,如下所示:

    OMNamespace poNs= factory.createOMNamespace("http://openuri.org/easypo", "po");

     

    现在可以构造包装器元素 line-item 了:

    OMElement lineItem= factory.createOMElement("line-item", poNs);

     

    接下来创建 line-item 元素相关的子元素和属性。

    最好用下面的方式创建元素属性:

    lineItem.addAttribute("quantity", "2", poNs);

     

    与其他元素一样创建子元素,然后按照下面的方式结合到父元素中:

       OMElement description= factory.

          createOMElement("description", poNs);

             description.setText("Burnham's Celestial Handbook, Vol 2");

             lineItem.addChild(description);

     

    类似地,也添加 price 子元素:

       OMElement price= factory.createOMElement("price", poNs);

             price.setText("19.89");

             lineItem.addChild(price);

     

    清单 2 显示了完整的代码片段。


    清单 2.通过程序创建 line item

     

       OMFactory factory = OMAbstractFactory.getOMFactory();

             OMNamespace poNs =

             factory.createOMNamespace("http://openuri.org/easypo", "po");

             OMElement lineItem =

             factory.createOMElement("line-item", poNs);

             lineItem.addAttribute("quantity", "2", poNs);

             OMElement description =

             factory.createOMElement("description", poNs);

             description.setText("Burnham's Celestial Handbook, Vol 2");

             lineItem.addChild(description);

             OMElement price = factory.createOMElement("price", poNs);

             price.setText("19.89");

             lineItem.addChild(price);

     

    输出

    现在可以使用 StAX writer 来序列化构造好的元素:


    清单 3.序列化 line item

     

       XMLOutputFactory xof = XMLOutputFactory.newInstance();

       XMLStreamWriter writer = xof.

          createXMLStreamWriter(System.out);

          lineItem.serialize(writer);

          writer.flush();

     

    从已有代码构造 AXIOM

    现在看看相反的过程,从数据流建立内存对象模型。

    最简单的情况下,只需要关心 XML 片段的反序列化。但是在 SOAP 处理中,需要反序列化 SOAP 消息或者经过 MTOM 优化的 MIME 信封。因为与 SOAP 处理关系特别密切,所以 AXIOM 为此提供内置支持,稍候将详细介绍。但首先要说明如何反序列化简单的 XML 片段,具体来说就是刚刚序列化的那个 XML 片段。

    首先构造一个解析器。AXIOM 支持用 SAX 和 StAX 解析器解析 XML。但是,SAX 解析不允许对象模型的延迟构造,因此在延迟构建很重要时,应该使用基于 StAX 的解析器。

    第一步是为数据流获得一个 XMLStreamReader:

    File file= new File("line-item.xml");

             FileInputStream fis= new FileInputStream(file);

             XMLInputFactory xif= XMLInputFactory.newInstance();

             XMLStreamReader reader= xif.createXMLStreamReader(fis);

     

    然后创建一个 builder 并将 XMLStreamReader 传递给它:

       StAXOMBuilder builder= new StAXOMBuilder(reader);

             lineItem= builder.getDocumentElement();

     

    现在可以使用 AXIOM API 来访问属性和子元素或者 XML Infoset 项了。可以这样访问属性:

       OMAttribute quantity= lineItem.getFirstAttribute(

             new QName("http://openuri.org/easypo", "quantity"));

       System.out.println("quantity= " + quantity.getValue());

     

    用类似的方式访问子元素:

       price= lineItem.getFirstChildWithName(

             new QName("http://openuri.org/easypo", "price"));

                   System.out.println("price= " + price.getText());

     

    清单 4 显示了完整的代码片段。


    清单 4. XML 文件构建 AXIOM

     

    File file = new File("line-item.xml");

       FileInputStream fis = new FileInputStream(file);

       XMLInputFactory xif = XMLInputFactory.newInstance();

       XMLStreamReader reader = xif.createXMLStreamReader(fis);

       StAXOMBuilder builder = new StAXOMBuilder(reader);

       OMElement lineItem = builder.getDocumentElement();

       lineItem.serializeWithCache(writer);

       writer.flush();

       OMAttribute quantity = lineItem.getFirstAttribute(

             new QName("http://openuri.org/easypo", "quantity"));

       System.out.println("quantity= " + quantity.getValue());

       OMElement price = lineItem.getFirstChildWithName(

             new QName("http://openuri.org/easypo", "price"));

       System.out.println("price= " + price.getText());

       OMElement description = lineItem.getFirstChildWithName(

             new QName("http://openuri.org/easypo", "description"));

       System.out.println("description= " + description.getText());

     

    AXIOM 最好的一点是,努力在延迟构造这类高端技术上提供用户友好的 API。但是要充分发挥其潜能,必须了解底层体系结构。

     


     

     

    进一步考察 AXIOM

    缓冲是 AXIOM 的核心概念之一。但是,要理解缓冲必须在树的延迟构造和 AXIOM API 上下文中来思考。AXIOM 提供多种访问底层 XML Infoset 的 API。上面使用的是基于树的 API,所有其他竞争的对象模型如 DOM 和 JDOM 都提供了这样的 API。但是,AXIOM 还允许通过 SAX 或 StAX API 访问信息。如图 1 所示。


    1. AXIOM,输入和输出

    如果要使用一种 XML 解析 API,为何还要构造对象模型呢?为了使用不同 API 访问对象模型的不同部分。比如,考虑 SOAP 栈的情况:SOAP 消息在被目标服务消费之前可能会经过多个处理程序的处理。这些处理程序通常使用基于树的 API(特别是 SOAP with Attachments API for Java,或 SAAJ)。服务实现还可能使用数据绑定工具将 SOAP 消息负荷中的 XML 文档转化成对象,如 POJO。因为用户不使用基于树的对象模型来访问这部分文档,所以构造完整的树会因为数据重复而浪费内存。最直接的解决方法是向数据绑定工具公开底层的原始 XML 流。这就是 AXIOM 的闪光之处。

    为了获得最佳的性能和内存使用,需要让数据绑定工具直接访问底层的 XML 流。AXIOM 完全允许这样做。延迟构建仅仅意味着只有在访问的时候才构造要访问的这部分树。因此如果不需要访问 SOAP 消息体,SOAP 消息的这部分就不会被构建。如果用户开始使用 SAX 或 StAX 访问消息体,而它还没有构建,AXIOM 将把用户直接连接到底层的解析器,以便提供最佳的性能。如图 2 所示:


    2.通过 AXIOM 访问底层的解析器

    但是,如果用户希望再回来访问树的同一部分就可能出现问题。因为解析器已经直接连接了用户,AXIOM 退出了,就是说所有信息都从低层的流直接流向用户。因此当用户回来请求同样的信息时,无论第二次选择什么样的 API,AXIOM 都不能提供该信息。注意这两种可能性差不多相等。比如,多数情况下 SOAP 体的处理中只有最终的服务实现才会涉及到负荷。服务可以使用数据绑定或其他 XML 处理 API 如 SAX、StAX 或 XPath 来处理消息体。这种情况下,消息体很少被访问两次,AXIOM 提供的优化具有最好的性能。

    但是,假设在处理程序链中插入一个日志处理程序,使用 StAX writer 记录整个 SOAP 消息。如果服务实现尝试访问消息体,而消息体不存在!

    为了进一步说明这一点,下面是一个比较简单的例子,虽然有点牵强。

    StAXOMBuilder builder = new StAXOMBuilder(reader);

       lineItem = builder.getDocumentElement();

       lineItem.serialize(writer);

       writer.flush();

       price = lineItem.getFirstChildWithName(

          new QName("http://openuri.org/easypo", "price"));

       System.out.println("price= " + price.getText());

     

    由于延迟构造,获得 lineItem 元素的时候该元素还没有构造完成。因此后面使用 StAX writer 进行序列化时,AXIOM 把 StAX writer(它序列化 lineItem 元素)直接连接到 StAX reader(它最初被传递给 builder)。但是这个过程中,AXIOM 断开了自身和数据流的连接。现在当请求 price 子元素的时候,找不到这样的元素,因为 lineItem 的所有子元素都在序列化器中消失了。

    这种情况下,惟一的办法是避免序列化过程中 AXIOM 完全和数据流脱离开。用 AXIOM 的术语称为缓冲:无论是否在内存中建立了对象模型,AXIOM 都允许获得 StAX 事件或者 序列化 XML。因此,AXIOM 把策略(比如是否应该缓冲消息)和机制(如何缓冲)分离开来。它允许用户在开始使用原始 XML 处理 API(如 SAX 或 StAX)时决定是否缓冲树中未用到的部分以供将来引用。如果用户决定这样做,当树构造完成时可以再回来访问这些部分。但是,用户必须付出内存占用和性能的代价。另一方面,如果用户了解自己的目标,并确信只此一次需要访问树的这些部分,则可以选择关闭 缓冲来充分发挥 AXIOM 的效率。

    因此,上一段代码应改写为:

       StAXOMBuilder builder = new StAXOMBuilder(reader);

          lineItem = builder.getDocumentElement();

          lineItem.serializeWithCache(writer);

          writer.flush();

       price = lineItem.getFirstChildWithName(

          new QName("http://openuri.org/easypo", "price"));

       System.out.println("price= " + price.getText());

     

    方法 serializeWithCache 与对应的 serialize 不同,不会将 StAX reader 直接连接到 StAX writer。相反,从 reader 传递给 writer 的所有数据都保留 在 AXIOM 中。具体如何缓冲与用户无关。目前如果启用缓冲,AXIOM 就会像用户在通过文档 API 访问树的这些部分一样构造树。

     

     

     

    AXIOM StAX

    了解这些背景之后,现在看看 AXIO 的 StAX API。该 API 中最重要的方法如下:

    (OMElement).getXMLStreamReader();

    (OMElement).getXMLStreamReaderWithoutCaching();

     

    通过 StAX API 对某个元素调用第一个方法,可以访问该元素的 XML Infoset,同时缓冲(如果需要)树中未构造的部分以供将来使用。顾名思义,第二个方法用于访问同样的信息,但是通过关闭缓冲机制优化了性能。在编写需要使用数据绑定框架的存根和 skeleton 程序时,这是最有用的方法。

    但是请注意,如果在调用上述方法之前已经建立了树,AXIOM 将模拟 StAX 解析器。因此有些树节点的事件是通过模拟而来的,而对于另一些节点则直接连接到底层的解析器。AXIOM 的优点在于这些内部处理对用户是透明的。但是,在切换到原始 API 时,必须指明是否需要缓冲数据。

    为了说明 StAX API 的用法,我将展示如何使用 XMLBeans 生成的代码连接到 AXIOM。


    清单 5.XMLBeans 生成的订单代码

     

    public class PurchaseOrderSkel {

       public void submitPurchaseOrder(

             PurchaseOrderDocument doc) throws Exception {

                      

       }

          public void submitPurchaseOrderWrapper(

                   OMElement payload) {

                try {

                      XMLStreamReader reader= payload.

                            getXMLStreamReaderWithoutCaching();

                      PurchaseOrderDocument doc

                         = PurchaseOrderDocument.Factory.parse(reader);

                               submitPurchaseOrder(doc);

                } catch (Exception ex) {

                      ex.printStacktrace();

                }

          }

       }

     

    清单 5 中的代码(通常用代码生成工具生成)展示了一个 skeleton,它使用 XMLBeans 生成的类(即 PurchaseOrderDocument)进行数据绑定。这个 skeleton 包含两个服务实现方法。第一个允许服务实现者使用数据绑定对象,第二个则允许直接访问 AXIOM API。主要看看这几行:

                      XMLStreamReader reader= payload.

                            getXMLStreamReaderWithoutCaching();

                      PurchaseOrderDocument doc

                         = PurchaseOrderDocument.Factory.parse(reader);

     

    为了创建对象,首先对 SOAP 栈(如 Apache Axis)压入服务实现的载荷获得对 StAX API 的引用。因为现在在处理链的最末端,所以可以安全地把解析器直接连接到 XMLBeans 解除封送器以获得最佳性能。

    对于 清单 5 中的 skeleton,其存根代码类似于 清单 6


    清单 6.存根代码

     

    public class PurchaseOrderStub {

             public void submitPurchaseOrder(

                PurchaseOrderDocument doc) throws Exception {

                      SOAPEnvelope envelope = factory.getDefaultEnvelope();

                      XMLStreamReader reader = doc.newXMLStreamReader();

                      StAXOMBuilder builder = new StAXOMBuilder(reader);

                      OMElement payload= builder.getDocumentElement();

                      envelope.getBody().addChild(payload);

                      // ...

             }

          }

     

    主要看看这几行:

                      XMLStreamReader reader = doc.newXMLStreamReader();

                      StAXOMBuilder builder = new StAXOMBuilder(reader);

                      Element payload= builder.getDocumentElement();

     

    从这段代码可以看出,经过 StAX API 从对象到 AXIOM,与从 XML 到 AXIOM 没有什么区别。

    但是初看起来不那么明显的是延迟构造仍然在起作用!即使在将载荷插入 SOAP 信封的过程中创建了 OMElement,内存中也没有重复的信息项。这是由于延迟构造和 AXIOM 内的多路技术造成的,它将从一个 API 输入的数据直接转发给另一个 API 输出。当消息最终写入流的时候,XMLBeans 提供的 XMLStreamReader 直接连接到传输 writer,后者将消息写入套接字 —— 假设此过程中没有要查看消息的处理程序。这意味着直到此时,数据仍然存放在 XMLBeans 对象中,真是好极了!

     

     

     

    AXIOM 和数据绑定

    这里讨论 AXIOM 的 SAX API,因为有些数据绑定框架不能使用其他的 API,比如 JAXB。虽然上述情况下使用 SAX 显然不会达到最佳性能,但从 AXIOM 到对象使用 SAX 并没有造成性能损失,因为这一步在任何情况下都是必需的。

    如果使用 JAXB,那么存根程序就要使用 SAXOMBuilder 从数据绑定对象建立 AXIOM。清单 7 示范了这个过程。


    清单 7. AXIOM JAXB

     

    public class PurchaseOrderStub {

          public void submitPurchaseOrder(

                PurchaseOrder doc) throws Exception {

                      SOAPEnvelope envelope = factory.getDefaultEnvelope();

                      SAXOMBuilder builder = new SAXOMBuilder();

                      JAXBContext jaxbContext = JAXBContext.newInstance("po");

                      Marshaller marshaller = jaxbContext.createMarshaller();

                      marshaller.marshal(doc, builder);

                      OMElement payload= builder.getDocumentElement();

                      envelope.getBody().addChild(payload);

                      //...

          }

       }

     

    到目前为止,AXIOM 还不允许使用 OMElement 注册内容处理程序来处理收到的 SAX 事件。不过很容易编写一段胶水代码,从提供的 StAX 接口接收事件并驱动 SAX ContentHandler。有兴趣的读者可以从 参考资料 中的 JAXB 参考实现中找到这样的实现。

     

     

    结束语

    我介绍了与典型的 XML 对象模型相比 AXIOM 引入的一些很有前途的特性。注意本文仅仅介绍了部分特性。AXIOM 有很多更强大的特性,建议您从 Axis 2 源代码库(请参阅 参考资料)下载最新的源代码,进一步研究 AXIOM。

     

    参考资料

    学习

  • 相关阅读:
    [图解数据结构] 线性表
    利用爬虫获取网上医院药品价格信息 (上)
    手把手教你写网络爬虫(1):网易云音乐歌单
    用python实现与小米网关通讯
    eclipse添加lombok无法启动
    mongodb数据出现undefined如何查询
    初探active mq
    base64和图片互转
    设置Linux下Mysql表名不区分大小写
    Windows下安装MySQL5.6绿色版
  • 原文地址:https://www.cnblogs.com/antis/p/5228191.html
Copyright © 2020-2023  润新知