AXIOM的全称为AXIs Object Model,最初是作为Apache Axis 2的XML对象模型开发的。但是后来变成了WS Commons Project的一部分,以便收益于Axis2外的其他项目。
Overview and Features
AXIOM是一个实现了延迟构造和拉解析(pull parsing)的轻量级XML解析器。延迟构造是AXIOM的最重要的特性之一,它可以实现对象在使用时才构造。而这个延迟构造的功能实现是基于标准的拉式解析器——StAX。
What is Pull Parsing?
简单介绍一下“拉式解析”的概念。一个XML文档可以通过“拉式”或“推式”中任意一种方式来解析。“拉式”是目前流行的XML解析方式。传统的XML解 析框架,例如SAX和DOM都是“推式”的,那意味着解析过程是由解析器本身来控制的。这样的实现方式看似不错,并且使用方便,但是在解析大型XML文档 时效率就差了,因为整个文档对象模型都要生成在内存里。而“拉式解析”刚好颠倒了解析过程中的控制关系,解析器只对用户指定的部分进行解析(好比传送带上 的一盘盘寿司,我只取我感兴趣的那盘,而不是把经过我面前的都拿下来,筛选后再把不感兴趣的放上去,留下想吃的那部分)。这样用户可以决定是保存或是抛弃 解析器生成的事件。OM(对象模型)便是基于“拉式解析”的。
Working with AXIOM
要使用AXIOM需要下载它的API包。AXIOM开始是作为AXIS2的一部分出现的,但现在已经可以单独下载了,当然在AXIS2的发布包里还是可以找到它的。
Creating an AXIOM
我们可以通过三种方式创建一个AXIOM:Pull Event stream,Push Event stream或者由程序自动创建。本文主要演示通过第一种和第三种方式来构建一个AXIOM,它们也是创建AXIOM最常用的方式。
Creating an AXIOM from an Input Stream
下面的代码演示了如何从一个文件输入流来创建一个AXIOM:
//首先需要创建一个parser或是reader,这里我们创建一个parser.
XMLStreamReader parser = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(file));
//然后需要创建一个builder来使用刚才创建的parser,这里我们使用StAXOMBuilder。
StAXOMBuilder builder = new StAXOMBuilder(parser);
//最后便可以通过builder获取一个document element。
OMElement documentElement = builder.getDocumentElement();
注意,当我们从builder获取到document element时,builder仅仅是返回一个指向,我们将要操作的XML数据仍然在数据流里,没有被取出来,对象树也没有被创建。对象树只有到我们导航或是构建AXIOM时创建。
Creating an AXIOM Using a String
现在,让我们通过一个字符串来创建AXIOM,这是一个非常简单、方便的方式。
String xmlString = "<book>" +
"<name>Quick-start Axis</name>" +
"<isbn>978-1-84719-286-8</isbn>" +
"</book>";
ByteArrayInputStream xmlStream = new ByteArrayInputStream(xmlString.
getBytes());
//create a builder. Since we want the XML as a plain XML, we can just use the plain OMBuilder
StAXBuilder builder = new StAXOMBuilder(xmlStream);
//return the root element.
OMElement documentElement = builder.getDocumentElement();
Creating an AXIOM Programmatically
//获取一个 factory
OMFactory factory = OMAbstractFactory.getOMFactory();
//使用这个factory来创建一个命名空间对象。
OMNamespace axis2 = factory.createOMNamespace("axis2","ns");
//使用这个factory来创建三个元素。
OMElement root = factory.createOMElement("book",axis2);
OMElement name = factory.createOMElement("name",axis2);
OMElement isbn = factory.createOMElement("isbn",axis2);
在上面的代码里,我们可以看到一组factory.create*方法。这些方法可以用来创建不同的XML对象。在AXIOM里,推荐使用这样的方法来创建AXIOM对象,因为这样可以使得在AXIOM的其它不同实现中切换变得简单。
Adding a Child Node and Attributes
到目前为止,我们已经学习了创建AXIOM和使用StAX的API,但这些对于使AXIOM工作起来还是不够的。我们还需要学习向AXIOM添加子节点。
在OMElement 接口中已经定义了基本的添加和删除方法:
public void addChild(OMNode omNode);
public void addAttribute(OMAttribute omAttribute);
下面让我们实现向root元素添加子节点:
root.addChild(name); root.addChild(isbn);
*add()方法总是将新添加的子节点作为最后一个子节点加入到父节点中。
Working with OM Namespaces
由于对命名空间的处理也是解析XML的关键部分,因此AXIOM也提供了一系列API来处理命名空间:
public OMNamespace declareNamespace(String uri, String prefix);
public OMNamespace declareNamespace(OMNamespace namespace);
public OMNamespace findNamespace(String uri, String prefix) throws OMException;
以上方法对于使用其它解析方法处理过XML的朋友来说应该不难理解,但需要注意的是,一个已经添加过一次的命名空间声明不能被再次添加。
findNamespace方法是一个非常便捷的方法用来在整个对象树中找到一个命名空间对象。
在序列化过程中,一个由工厂方法直接创建的命名空间不会被立即声明,只有当序列化到这个命名空间被使用的地方(即遇到它的前缀)时才会被声明。
如果我们序列化我们创建的元素,我们会得到:
<ns:book xmlns:ns="axis2"> <ns:name></ns:name> <ns:isbn></ns:isbn> </ns:book>
Working with Attributes
下面让我们来看一下如何给book元素(代码中的root)添加一个属性:
OMAttribute type = factory.createOMAttribute("type",null, "web-services"); root.addAttribute(type);
这时我们再序列化一下代码,我们将得到:
<ns:book xmlns:ns="axis2" type="web-services"> <ns:name></ns:name> <ns:isbn></ns:isbn> </ns:book>
Traversing the AXIOM Tree
在前面的章节,我们已经学习了如何创建一个元素,如何添加子节点,如何创建命名空间和属性。现在,就让我们来遍历一下整个AXIOM树。
遍历对象结构可以通过常用的获取子节点列表的方法,但在AXIOM里,获取子节点得到的是一个iterator。下面的代码演示来如何通过给定的节点来访问子节点。子节点的类型为OMNode,可以是OMText或OMElement中的任意一种。
Iterator children = root.getChildren(); while(children.hasNext()){ OMNode node = (OMNode)children.next(); }
需 要补充的是,每一个OMNode都和相邻兄弟节点相连,所以在访问相邻节点时可以使用nextSibling() 或是 PreviousSibling()方法。还可以通过直接指定字节点的名称来获取子节点,方法getChildWithName (Qname)实现来这个功能,它会返回一个iterator.使用这些iterator的优点是,整个对象结构不会立马构造,只有当需要的时候才会构 造。
Serialization
到目前为止,我们对创建和遍历AXIOM树已经有了一定的了解。现在就让我们将AXIOM树序列化或是写入一个输出流。
AXIOM可以被序列化成一个纯对象模型或是一个拉事件流。序列化过程使用一个XMLStreamWriter对象来写输出,因此相同的序列化机制也可以来写不同类型的输出,比如text,binary……
AXIOM提供了个一缓存标志来控制内存中的AXIOM。OMNode提供了两个方法serializeAndConsume和serialize。当 serializeAndConsume方法被调用时,缓存标志被重置,序列化器不会缓存这个流,因此如果缓存标志不被设置,这个对象模型就不会被创建。 在这种情况下,XML流不会创建对象模型,而直接被序列化到输出流里。所以如果调用了serializeAndConsume方法,我们就只能序列化一次 AXIOM树,因为内存中并没有生成AXIOM对象树。但是,我们可以多次调用serialize方法。文字可能不好理解,下面让我看段代码:
String xmlStream = "<ns:book xmlns:ns=\"axis2\" type=\"web-services\ "><ns:name></ns:name><ns:isbn></ns:isbn></ns:book>"; //从字符串创建一个输入流 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (xmlStream.getBytes()); //创建一个builder. StAXBuilder builder = new StAXOMBuilder(byteArrayInputStream); //获取root元素. OMElement root = builder.getDocumentElement(); //在这里,我们调用两次serialize root.serialize(System.out); root.serialize(System.out);
如果我们执行上面的代码,我们会在控制台看到:
<ns:book xmlns:ns="axis2" type="web-services"><ns:name></ns:name><ns: isbn></ns:isbn></ns:book> <ns:book xmlns:ns="axis2" type="web-services"><ns:name></ns:name><ns: isbn></ns:isbn></ns:book>
但 是如果我们先调用serializeAndConsume()方法,再调用serialize()方法,就会出现一个异常。这是因为一旦 serializeAndConsume()被调用,缓存标志就会被重置。所以紧接着调用serialize()方法时,我们没有东西可以被序列化来,因 此就得到了一个异常。
//将上面的代码最后两行做如下改动,便会出现异常。
root.serializeAndConsume(System.out); root.serialize(System.out);
AXIOM and SOAP
前面已经说过,AXIOM本来是作为Axis2的一部分——它的专属XML解析器而出现的,而Axis2是一个SOAP框架,那解析SOAP消息自然也是 AXIOM的主要功能之一了。SOAP也是XML,只不过有它独特的结构而已,所以我们可以很方便的从AXIOM获取一个SOAP层的API。
下面我们直接看代码,用AXIOM分别创建一个SOAP1.1和SOAP1.2文档。
Creating a SOAP 1.1 Document:
OMFactory factory = OMAbstractFactory.getOMFactory(); OMNamespace axis2 = factory.createOMNamespace("axis2", "ns"); OMElement root = factory.createOMElement("book", axis2); OMAttribute type = factory.createOMAttribute("type",null, "web-services"); root.addAttribute(type); OMElement name = factory.createOMElement("name", axis2); OMElement isbn = factory.createOMElement("isbn", axis2); root.addChild(name); root.addChild(isbn); SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory(); //get the default envelope SOAPEnvelope env = soapFactory.getDefaultEnvelope(); //add the created child env.getBody().addChild(root); System.out.println( env);
Creating a SOAP 1.2 Document:
OMFactory factory = OMAbstractFactory.getOMFactory(); OMNamespace axis2 = factory.createOMNamespace("axis2", "ns"); OMElement root = factory.createOMElement("book", axis2); OMAttribute type = factory.createOMAttribute("type",null,"web- services"); root.addAttribute(type); OMElement name = factory.createOMElement("name", axis2); OMElement isbn = factory.createOMElement("isbn", axis2); root.addChild(name); root.addChild(isbn); SOAPFactory soapFactory = OMAbstractFactory.getSOAP12Factory(); //get the default envelope SOAPEnvelope env = soapFactory.getDefaultEnvelope(); //add the created child env.getBody().addChild(root); System.out.println( env);
两段代码唯一的区别是,使用的工厂不同而已。
Summary
通过以上的介绍,相信大家对AXIOM应该有了一个比较清晰的了解了,这样上手Axis2应该更容易了。