关闭

用 AXIOM 促进 XML 处理

1003人阅读 评论(0) 收藏 举报

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 退出了,就是说所有信息都从低层的流直接流向用户。因此当用户回来请求同样的信息时,无论第二次选择什么样的 APIAXIOM 都不能提供该信息。注意这两种可能性差不多相等。比如,多数情况下 SOAP 体的处理中只有最终的服务实现才会涉及到负荷。服务可以使用数据绑定或其他 XML 处理 API SAXStAX 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

 

参考资料

学习

 
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:101823次
    • 积分:1335
    • 等级:
    • 排名:千里之外
    • 原创:16篇
    • 转载:57篇
    • 译文:0篇
    • 评论:17条
    文章分类
    最新评论