详解Axis2实现Web Services之AXIOM篇

(原文地址:http://danlley.iteye.com/blog/102164

AXIOM——AXis 对象模型(AXis Object Model,AXIOM)是 Apache Axis 2 的 XML 对象模型,Axiom采用pull解析方式,基于StAX(JSR173),其目标是提供强大的特性组合彻底改变 XML 处理技术(Axiom和StAX紧密相关,要使用Axiom,StAX相关的jar包也必须在classpath下)。

 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 超越了现有的 XML 处理技术,它把延迟构建和一种快速、轻型的可定制对象模型结合了起来, 提供了一种可以按需扩展的虚拟文档模型,仅构建客户端应用程序所请求的树结构文档模型表示。这种虚拟的文档模型工作于 XML 文档的元素级。当解析器报告元素开始标记时创建元素表示,但是该元素的初始形式仅仅只是一个壳,其中保存了对解析器的引用。

如果应用程序需要获取元素内容 的细节信息,它只需要通过调用接口(如 org.apache.axiom.om.OMContainer.getChildren() 方法)的方法,就可以请求相应的信息。然后,在对这个方法调用的响应中,解析器将构建该元素的子内容。

解析器按照文档顺序(与 XML 文档文本中项目的出现顺序相同)传递数据,所以 AXIOM 所实现的按需构造需要某种灵活的处理方法。
 
Axiom的一些特性:
    1、Lightweight(轻量),更少的内存需要。
    2、Deferred building(延迟构建),可以说是最重要的OM特性,
    3、Pull based(pull模式),OM基于StAX--标准的pull parser API。
 

开始构建:Axiom在服务构建上看起来会比POJOs复杂些。

1.创建服务类:
java 代码
  1. package org.danlley.util.service;   
  2. public class HelloWorldAxiom{   
  3.     public OMElement getHelloMessage(OMElement element) throws XMLStreamException{   
  4.         element.build();   
  5.         element.detach();   
  6.         return element;   
  7.     }   
  8. }  
我们在创建过程中顺便也就将我们例子中所要用到的几个关键类引入:
java 代码
  1. import javax.xml.stream.XMLStreamException;   
  2. import org.apache.axiom.om.OMAbstractFactory;   
  3. import org.apache.axiom.om.OMElement;   
  4. import org.apache.axiom.om.OMFactory;   
  5. import org.apache.axiom.om.OMNamespace;  

这样,我们已经有一个基本的服务类框架结构了,在该结构中我们定义了一个服务提供者getHelloMessage。他在获取了一些特定的客户端信息时为客户端返回一些特定信息。
 
 

2.定义Ant脚本:与POJOs的实现方式比较来看,这个Ant脚本看上去要比他复杂了些
xml 代码
  1. <project basedir="." default="generate.service">  
  2.  <property environment="env" />  
  3.  <property name="AXIS2_HOME" value="${env.AXIS2_HOME}" />  
  4.  <property name="build.dir" value="build" />  
  5.  <path id="axis2.classpath">  
  6.   <fileset dir="${AXIS2_HOME}/lib">  
  7.    <include name="*.jar" />  
  8.   </fileset>  
  9.  </path>  
  10.  <path id="client.class.path">  
  11.   <fileset dir="${AXIS2_HOME}/lib">  
  12.    <include name="*.jar" />  
  13.   </fileset>  
  14.   <pathelement location="${build.dir}/classes" />  
  15.  </path>  
  16.  <target name="compile">  
  17.   <mkdir dir="${build.dir}" />  
  18.   <mkdir dir="${build.dir}/classes" />  
  19.   <javac debug="on" fork="true" destdir="${build.dir}/classes" srcdir="${basedir}/src" classpathref="axis2.classpath">  
  20.   </javac>  
  21.  </target>  
  22.  <target name="generate.service" depends="compile">  
  23.   <copy toDir="${build.dir}/classes" failonerror="false">  
  24.    <fileset dir="${basedir}/resources">  
  25.     <include name="**/*.xml" />  
  26.     <include name="**/*.wsdl" />  
  27.    </fileset>  
  28.   </copy>  
  29.   <jar destfile="${build.dir}/HelloWorldAxiom.aar">  
  30.    <fileset excludes="**/Test.class" dir="${build.dir}/classes" />  
  31.   </jar>  
  32.  </target>  
  33.  <target name="run.client" depends="compile">  
  34.   <java classname="samples.quickstart.clients.AXIOMClient">  
  35.    <classpath refid="client.class.path" />  
  36.   </java>  
  37.  </target>  
  38.  <target name="clean">  
  39.   <delete dir="${build.dir}" />  
  40.  </target>  
  41. </project>  
 
Target依赖关系如下:
generate.service→compile
run.client→compile

clean是一个单独的独立target,当然如果你希望在程序被build之前自动进行clean操作可以修改target间的依赖关系。

接下来我们要根据我们定义的接口来生成我们所要的WSDL,关于WSDL的相关话题我以前已经在我的一片关于Web Services的专题中进行了较为详细的阐述,大家如果有兴趣可以访问:
 
 

3.根据类框架生成服务所需的WSDL:

在我们定义服务类的时候确定了我们将来要生成的服务aar包为 HelloWorldAxiom.aar,因此也就决定了WSDL的名字一定是HelloWorldAxiom.wsdl,在命令行切换到工程编译路径下:D:\eclipse\workspace\axis2axiomlab\build\classes>
运行如下命令:java2wsdl -cp . -cn org.danlley.util.service.HelloWorldAxiom -of HelloWorldAxiom.wsdl

说明:其中-cp是指classpath -cn是指classname
 
如果你运行后得到的运行结果如下,则说明运行成功,否则就需要查看 AXIS2_HOME是否正确配置了:

D:\eclipse\workspace\axis2axiomlab\build\classes>java2wsdl -cp . -cn org.danlley
.util.service.HelloWorldAxiom -of HelloWorldAxiom.wsdl
Using AXIS2_HOME:   D:\axis2-1.1.1
Using JAVA_HOME:    C:\Program Files\Java\jdk1.5.0_06
D:\eclipse\workspace\axis2axiomlab\build\classes>

 
 
 
得到HelloWorldAxiom.wsdl后我们将其放入resources/META-INF目录下。对程序员来说,比较有价值的部分如下:
xml 代码
  1. <xs:element name="getHelloMessage">  
  2.  <xs:complexType>  
  3.   <xs:sequence>  
  4.    <xs:element name="element" nillable="true" type="xs:anyType" />  
  5.   </xs:sequence>  
  6.  </xs:complexType>  
  7. </xs:element>  
  8. <xs:element name="getHelloMessageResponse">  
  9.  <xs:complexType>  
  10.   <xs:sequence>  
  11.    <xs:element name="return" nillable="true" type="xs:anyType" />  
  12.   </xs:sequence>  
  13.  </xs:complexType>  
  14. </xs:element>  
在getHelloMessage节点中,我们可以知道接口所需传入的参数,这里我们可以注意到,其实用自动生成工具生成的WSDL中,参数通常都是anyType类型的。getHelloMessageResponse节点则是提供了服务类在处理完相关业务后最后给我们返回出来的数据。
 

接下来就是
xml 代码
  1. <wsdl:port name="HelloWorldAxiomSOAP11port" binding="axis2:HelloWorldAxiomSOAP11Binding">  
  2.  <soap:address location="http://localhost:8080/axis2/services/HelloWorldAxiom" />  
  3. </wsdl:port>  
这段代码对编写客户端以及通过IE访问服务都非常有用。至于其他的,我在以前的专题中都或多或少有过一定的介绍,为了保证我们主题的完整性,我这里就不用在一一展开了。
 

4.在resources/META-INF目录下编写services.xml
xml 代码
  1. <service name="HelloWorldAxiom" scope="application">  
  2.  <description>Stock Quote Service</description>  
  3.  <operation name="getHelloMessage">  
  4.   <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver" />  
  5.  </operation>  
  6.  <parameter name="ServiceClass">org.danlley.util.service.HelloWorldAxiom</parameter>  
  7. </service>  
Axis2提供了两种主要的信息收发方式(MEP,Message Exchange Pattern):In-Out、In-Only。
由于我们定义的服务类不仅需要输入参数而且还需要将执行的结果返回到客户端,因此,我们在这里使用In-Out Pattern。Axis2为支持这种方式提供了一个org.apache.axis2.receivers.RawXMLINOutMessageReceiver类。文件中定义的ServiceClass由于在POJOs专题中我已经做过了说明,因此我这里就不再详述。

现在我们工程的整体框架已经构建完毕。
 
5.实现服务提供接口getHelloMessage
java 代码
  1. public OMElement getHelloMessage(OMElement element) throws XMLStreamException{   
  2.     element.build();   
  3.     element.detach();   
  4.     OMElement symbolElement=element.getFirstElement();   
  5.     String helloworld=symbolElement.getText();   
  6.     System.out.println("We got the messages :"+(helloworld!=null?helloworld:"No Msg!"));   
  7.     //build return msg   
  8.     String returnText="You are Perfect, Danlley!";   
  9.     OMFactory fac=OMAbstractFactory.getOMFactory();   
  10.     OMNamespace omNs=fac.createOMNamespace("http://service.util.danlley.org/xsd","ns");   
  11.     OMElement method=fac.createOMElement("getHelloMessageResponse",omNs);   
  12.     OMElement value=fac.createOMElement("return",omNs);   
  13.     value.addChild(fac.createOMText(value,returnText));   
  14.     method.addChild(value);   
  15.     return element;   
  16. }  
 

6.编写客户端:
java 代码
  1. package samples.quickstart.clients;   
  2. import org.apache.axiom.om.OMAbstractFactory;   
  3. import org.apache.axiom.om.OMElement;   
  4. import org.apache.axiom.om.OMFactory;   
  5. import org.apache.axiom.om.OMNamespace;   
  6. import org.apache.axis2.Constants;   
  7. import org.apache.axis2.addressing.EndpointReference;   
  8. import org.apache.axis2.client.Options;   
  9. import org.apache.axis2.client.ServiceClient;   
  10. public class AXIOMClient{   
  11.     private static EndpointReference targetEPR=new EndpointReference("http://localhost:8080/axis2/services/HelloWorldAxiom");   
  12.     public static OMElement getPricePayload(String msg){   
  13.         OMFactory fac=OMAbstractFactory.getOMFactory();   
  14.         OMNamespace omNs=fac.createOMNamespace("http://service.util.danlley.org/xsd","tns");   
  15.         OMElement method=fac.createOMElement("getHelloMessage",omNs);   
  16.         OMElement value=fac.createOMElement("msg",omNs);   
  17.         value.addChild(fac.createOMText(value,msg));   
  18.         method.addChild(value);   
  19.         return method;   
  20.     }   
  21.     public static void main(String[] args){   
  22.         try{   
  23.             OMElement getPricePayload=getPricePayload("Hello Danlley!");   
  24.             Options options=new Options();   
  25.             options.setTo(targetEPR);   
  26.             options.setTransportInProtocol(Constants.TRANSPORT_HTTP);   
  27.             //Optionally:TRANSPORT_JMS;TRANSPORT_LOCAL;TRANSPORT_MAIL;TRANSPORT_TCP;   
  28.             ServiceClient sender=new ServiceClient();   
  29.             sender.setOptions(options);   
  30.             System.out.println("done");   
  31.             Thread.sleep(3000);   
  32.             OMElement result=sender.sendReceive(getPricePayload);   
  33.             String response=result.getFirstElement().getText();   
  34.             System.out.println("Current msg of danlley: "+response);   
  35.         }catch(Exception e){   
  36.             e.printStackTrace();   
  37.         }   
  38.     }   
  39. }  

运行ant脚本运行结果如下:

服务器端:
信息: Server startup in 6922 ms
We got the messages :Hello Danlley!

客户端:
done
Current msg of danlley: You are Perfect, Danlley!
 
 
7.更加复杂点的例子
 
单条消息的解析过程过于简单,在我们的实际工程应用中事实上是没有任何实用价值的,那么如果是在一个节点中压入多条信息那么我们应该怎么来处理呢,作为一个Axis2的新手如果你要是自己亲自动手试一下就会发现,好像Axiom提供的接口在面对这种问题的时候似乎让人牙痒。
现在我们在服务类中再增加一个getMoreMessage方法,其接收的参数约定如下:
xml 代码
  1. <tns:getMoreMessage xmlns:tns="http://service.util.danlley.org/xsd">  
  2.     <tns:msg>Hello Danlley</tns:msg>  
  3.     <tns:userPWD>[email protected]</tns:userPWD>  
  4.     <tns:transactionKey>234545435464562</tns:transactionKey>  
  5. </tns:getMoreMessage>  
 
 
 
也就是说消息中存在3个节点的信息需要服务器端解析。一个比较笨的办法如下:
java 代码
  1. for(Iterator it=element.getChildElements();it.hasNext();){   
  2.     OMElement temp=(OMElement)it.next();   
  3.     if(temp.getLocalName().equalsIgnoreCase("msg")){   
  4.         symbol=temp.getText();   
  5.     }   
  6.     if(temp.getLocalName().equalsIgnoreCase("userPWD")){   
  7.         userPWD=temp.getText();   
  8.     }   
  9.     if(temp.getLocalName().equalsIgnoreCase("transactionKey")){   
  10.         transactionKey=temp.getText();   
  11.     }   
  12. }  

在该算法中,同一个分支中的3个同级节点在解析过程中就需要循环3次,那么可以试想一下,如果我们这里需要传送1000个节点,然后在同一时间有成千上万的人同时请求访问该服务。那么这个循环的代价就已经不可小视了。也就是说,对于小数据量在小访问量的情况下这种方式还可以勉强应付,但是对于在大数据流大访问量的情况下,这种处理方式就显得有些力不从心了。那么有什么好的办法没有,答案当然是肯定的。
基本思路就是拿到了客户端发送过来的数据后不直接使用Axiom的接口,而是使用DOM4J去处理。具体做法如下:
java 代码
  1. Document doc=DocumentHelper.parseText(element.toString());   
  2. String msg=doc.selectSingleNode("//tns:msg").getText();  
其中的element类型就是OMElement。
 
 
实现getMoreMessage方法:
java 代码
  1. public OMElement getMoreMessage(OMElement element) throws XMLStreamException{   
  2.     try{   
  3.         element.build();   
  4.         element.detach();   
  5.         DocumentFactory dom=new DocumentFactory();   
  6.         dom.createDocument(element.toString());   
  7.         System.out.println(element.toString());   
  8.         Document doc=DocumentHelper.parseText(element.toString());   
  9.         String msg=doc.selectSingleNode("//tns:msg").getText();   
  10.         String userPWD=doc.selectSingleNode("//tns:userPWD").getText();   
  11.         String transactionKey=doc.selectSingleNode("//tns:transactionKey").getText();   
  12.         System.out.println("\n msg="+msg+"\n userPWD="+userPWD+"\n transactionKey="+transactionKey);   
  13.            
  14.            
  15.         OMFactory fac=OMAbstractFactory.getOMFactory();   
  16.         OMNamespace omNs=fac.createOMNamespace("http://service.util.danlley.org/xsd","ns");   
  17.         OMElement method=fac.createOMElement("getHelloMessageResponse",omNs);   
  18.         OMElement value=fac.createOMElement("return",omNs);   
  19.         String returnText="MultiMessage for U, Danlley!";   
  20.         returnText=returnText+"\n msg="+msg+"\t userPWD="+userPWD+"\t transactionKey="+transactionKey;   
  21.         value.addChild(fac.createOMText(value,returnText));   
  22.         method.addChild(value);   
  23.         return method;   
  24.     }catch(OMException e){   
  25.         e.printStackTrace();   
  26.         return null;   
  27.     }catch(DocumentException e){   
  28.         e.printStackTrace();   
  29.         return null;   
  30.     }   
  31. }  
 

重新生成WSDL文件(过程省略--前面有^_^)并用改写后的生成的WSDL文件覆盖之前生成的文件
 

改写services.xml文件
xml 代码
  1. <service name="HelloWorldAxiom" scope="application">  
  2.  <description>Stock Quote Service</description>  
  3.  <operation name="getHelloMessage">  
  4.   <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver" />  
  5.  </operation>  
  6.  <operation name="getMoreMessage">  
  7.   <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver" />  
  8.  </operation>  
  9.  <parameter name="ServiceClass">org.danlley.util.service.HelloWorldAxiom</parameter>  
  10. </service>  
 
运行ant打包
 
执行客户端查看执行情况:
服务器端:

<tns:getMoreMessage xmlns:tns=" http://service.util.danlley.org/xsd"><tns:msg>Hel
lo Danlley</tns:msg><tns:userPWD>[email protected]</tns:userPWD><tns:transactionKe
y>234545435464562</tns:transactionKey></tns:getMoreMessage>
 msg=Hello Danlley
  [email protected]
 transactionKey=234545435464562

 

客户端:

done
Current msg of danlley: MultiMessage for U, Danlley!
 msg=Hello Danlley  [email protected]  transactionKey=234545435464562

 
 
 
 
8.查缺补漏
 
说了半天我们都是在以一种方式介绍Axiom的使用,但是对于In-Only模式,却没有给予特殊照顾,那么我就在这里在多说几句。仅仅就In-Out、In-Only两种模式之间的差异说明一下:

第一: 服务类接口,In-Out模式有返回值,但是In-Only没有(void类型)
第二:services.xml中messageReceiver在In-Only模式中使用org.apache.axis2.receivers.RawXMLINOnlyMessageReceiver类,而In-Out模式中使用org.apache.axis2.receivers.RawXMLINOutMessageReceiver类
第三:客户端程序的编写。In-Out模式需要收发消息(sender.sendReceive(element)),而In-Only只能发送消息(sender.fireAndForget(updatePayload))。
第四:WSDL。虽然会有差异,但是由于是自动生成的,因此可以看懂就OK了。

 
 
 
 
9.关于StAX
 
J2EE/XML开发者通常都是使用文档对象模型(DOM)API或简单的API for XML(SAX) API来分析XML文档。然而,这些API都有其缺点。其中,DOM API的缺点之一是消耗大量的内存,因为在该XML文档可以被导航之前,必须创建一个完整的XML文档的内存结构。而SAX API的缺点在于,它实例了一种推分析模型API,其中分析事件是由分析器生成的。比较之下,StAX则是基于一种拉分析模型。
  比较于推分析,拉分析具有如下一些优点:

  1. 在拉分析中,事件是由分析应用程序生成的,因此把分析规则提供到客户端而不是分析器。
  2. 拉分析的代码更简单并且它比推分析有更少的库。
  3. 拉分析客户端能同时读多个XML文档。
  4. 拉分析允许你过滤XML文档并且跳过分析事件。


针对于XML的流式API(StAX),是在2004年3月的JSR 173规范中引入,这是一种针对XML的流式拉分析API。StAX是JDK 6.0提供的一种新特征,你可以从此处下载它的测试版本试用。 https://mustang.dev.java.net/
 

一个推模型分析器不断地生成事件,直到XML文档被完全分析结束。但是,拉分析由应用程序进行调整;因此,分析事件是由应用程序生成的。这意味着,使用StaX,你可以推迟分析-在分析时跳过元素并且分析多个文档。在使用DOM API的时候,你必须把整个的XML文档分析成一棵DOM结构,这样也就降低了分析效率。而借助于StAX,在分析XML文档时生成分析事件。

StAX API的实现是使用了Java Web服务开发(JWSDP)1.6,并结合了Sun Java流式XML分析器(SJSXP)-它位于javax.xml.stream包中。其实,StaX仅仅是JDK 6.0所提供的XML新特征之一。新的JDK 6.0还提供了对针对于XML-Web服务的Java架构(JAX-WS)2.0,针对于XML绑定的Java API(JAXB) 2.0,XML数字签名API的支持,甚至还支持SQL:2003 'XML'数据类型。
 
 
有关于StAX分析器与其它分析器的比较在此不多介绍。以后准备单另开一个专题来专门研究。关于StAX的更多话题请查看以下地址:
 
 
注:此文档在8月之前将一直处于维护状态
 
 
参考资料:

你可能感兴趣的:(apache,Web,xml,应用服务器,ant)