Web Service深度編程——Axis序列化/反序列化器開(kāi)發(fā)指南
作者:薛谷雨
作者簡(jiǎn)介
薛谷雨是NORDSAN(北京)信息科技開(kāi)發(fā)有限公司高級JAVA研發(fā)工程師,正致力于企業(yè)級異構數據交換的服務(wù)器產(chǎn)品的研發(fā),在J2EE和WEB SERVICE方面有較為豐富的開(kāi)發(fā)經(jīng)驗,你可以通過(guò) rainight@126.com 與他取得聯(lián)系。
前言
Axis是Apache組織推出的SOAP引擎,Axis項目是Apache組織著(zhù)名的SOAP項目的后繼項目。Axis為開(kāi)發(fā)者提供了大量的序列化/反序列化器,能夠基本滿(mǎn)足大部分應用。但在某些情況下,對特定的對象,現有的序列化/反序列化器不能勝任,于是只有開(kāi)發(fā)人員自己實(shí)現專(zhuān)用于此對象的序列化/反序列化器插入到Axis中來(lái)完成序列化工作??紤]到Web Service是一門(mén)新興技術(shù),中文資料大多是泛泛的講解,關(guān)于序列化/反序列化器的開(kāi)發(fā)鮮有較為深入的介紹,本文提供一份較為完整的開(kāi)發(fā)指南,并提供了一個(gè)十分有用的實(shí)現,即序列化JDOM模型的Element,使其可以通過(guò)Web 服務(wù)在網(wǎng)絡(luò )上傳輸,我想這一擴展是許多采用JDOM作為XML解析工具的開(kāi)發(fā)人員都夢(mèng)寐以求的功能。通過(guò)本文的介紹和實(shí)例,希望能起到拋磚引玉的作用,讀者在閱讀完本文之后可以輕松的實(shí)現針對于任何非符合BEAN規范的對象的序列化/反序列化器。
本文所面對的讀者需要有一定的使用Axis做Web服務(wù)開(kāi)發(fā)的開(kāi)發(fā)經(jīng)驗,因此關(guān)于如何Axis的基礎知識并不在本文的介紹范圍,如果讀者對此感興趣,可以參考本文最后的參考資料部分,去相應的網(wǎng)站進(jìn)行學(xué)習。
序列化/反序列化器簡(jiǎn)介
序列化/反序列化器在英文中的對應翻譯是Serializer/Deserializer,一個(gè)序列化器的功能是遵循一定的映射規則和編碼風(fēng)格,將一種類(lèi)型的JAVA對象通過(guò)某種特定的機制,轉換成為XML描述的形式;反序列化器的功能是序列化器所做工作的逆操作,兩者相輔相成,成對出現。Axis中的序列化/反序列化器采用設計范式中的工廠(chǎng)模式,每一個(gè)Serializer唯一對應一個(gè)SerializerFactory;每一個(gè)Deserializer唯一對應一個(gè)DeserializerFactory。一種類(lèi)型的JAVA對象具體要采用哪個(gè)序列化/反序列化器需要在提供Web服務(wù)的服務(wù)器和調用Web服務(wù)的客戶(hù)端分別配置,關(guān)于這一部分如何配置,我將在本文后面的內容中進(jìn)行詳細介紹。Axis已經(jīng)為開(kāi)發(fā)者提供了豐富的序列化/反序列化器,對于java的基本數據類(lèi)型,絕大部分常用的容器類(lèi)(比如數組類(lèi)型,Vector類(lèi)型等)都提供了實(shí)現,特別是提供了對W3C的DOM對象(比如Document, Element等)和符合Bean規范的JAVA對象提供了功能完善的序列化/反序列化器,因此我們在需要的時(shí)候只要在配置文件中配置一下就可以直接使用。如果對象中包含其它類(lèi)型的對象,比如Vector中包含一組Bean對象,Axis會(huì )自動(dòng)疊代的調用序列化器,最終拼裝成唯一的XML表述。在還原成JAVA對象時(shí),也遵循這樣的疊代操作逆向進(jìn)行。關(guān)于A(yíng)xis到底內置了哪些序列化/反序列化器,您可以參照Axis的API文檔中包org.apache.axis.encoding.ser下的類(lèi)的名稱(chēng)"望文生義"的了解一下,在以后的開(kāi)發(fā)中做到心中有數。但對于一些特殊類(lèi)型的對象(其實(shí)我們自己開(kāi)發(fā)的很大一部分類(lèi)都是這種特殊類(lèi)型的對象,很少有絕對符合Bean規范的),需要通過(guò)Web服務(wù)進(jìn)行傳遞,我們不得不開(kāi)發(fā)自己的序列化/反序列化器。
開(kāi)發(fā)篇
開(kāi)發(fā)自己的序列化/反序列化器是一個(gè)激動(dòng)人心的工作,但是卻并不復雜,需要做的事情包括實(shí)現名成為org.apache.axis.encoding的包中的SerializerFactory,Serializer,DeserializerFactory和Deserializer這四個(gè)接口。下面我將結合一個(gè)實(shí)例來(lái)講解序列化/反序列化器的開(kāi)發(fā)方法,希望讀者能夠一邊參看本文提供的源代碼一邊學(xué)習。
JDOM作為一款比較"另類(lèi)"的XML解析工具(因為它不符合W3C的DOM模型,自己另立一套)默默地占領(lǐng)著(zhù)java世界里的xml解析器的半壁江山,由于其簡(jiǎn)潔的設計和方便靈活的API調用,已經(jīng)漸漸成為了許多開(kāi)發(fā)人員在進(jìn)行XML開(kāi)發(fā)的首選。但是Axis是建立在W3C的DOM模型的基礎之上,師出名們正派,自然不屑與JDOM為伍。因此當開(kāi)發(fā)人員想將自己已經(jīng)寫(xiě)好的基于JDOM的應用模塊采用Web服務(wù)的方式發(fā)布的時(shí)候,不可避免的會(huì )遇到如何將JDOM模型下的對象如Document, Element等序列化的問(wèn)題。在軟件工程師不會(huì )自己擴展Axis的序列化/反序列化器的時(shí)候,我們只能有兩個(gè)辦法達到這個(gè)目的,第一個(gè)就是更改以前應用模塊內的API設計,使暴露的入口參數和返回值參數都是W3C的對象類(lèi)型,但這種做法并不現實(shí),因為這一應用模塊往往不是獨立存在,牽一發(fā)將動(dòng)全身,導致舊有系統架構的崩塌;另一種做法就是為這個(gè)模塊做一個(gè)代理類(lèi),它做的工作就對外接收或返回DOM模型的對象,對內轉換成JDOM模型的對象,然后轉發(fā)給應用模塊,繁瑣且效率低下。當我們向Axis注入了針對于JDOM模型的序列化/反序列化器后,這一工作便可以由Axis代勞了。下面我們將逐個(gè)開(kāi)發(fā)這四個(gè)類(lèi):
JDomElementSerializerFactory
JDomElementSerializerFactory是一個(gè)工廠(chǎng)類(lèi),需要通過(guò)某種機制注冊到Axis引擎(具體方法見(jiàn)下面"服務(wù)器端應用篇");Axis通過(guò)調用它,來(lái)實(shí)例化JDomElementSerializer。Axis 提供了BaseSerializerFactory,這個(gè)類(lèi)是一個(gè)抽象類(lèi),并實(shí)現其中包含了一些可重用的代碼。我們自己開(kāi)發(fā)的工廠(chǎng)類(lèi)只需簡(jiǎn)單繼承這個(gè)類(lèi)就可以。構造函數中需要調用父類(lèi)的構造函數將序列器類(lèi)下面是它的源代碼:
package org.apache.axis.encoding.ser;public class JDomElementSerializerFactoryextends BaseSerializerFactory {public JDomElementSerializerFactory() {super(JDomElementSerializer.class);}}JDomElementSerializer
JDomElementSerializer實(shí)現org.apache.axis.encoding.Serializer接口,其核心API是serialize(),我們需要在這個(gè)方法的內部完成對JDOM模型的Element的序列化工作,序列化的結果要保存在入口參數傳入的序列化上下文對象(SerializationContext)中:
public void serialize(QName name, Attributes attributes, Object value,SerializationContext context) throws java.io.IOException {if (!(value instanceof Element))throw new IOException(Messages.getMessage("cant Serialize Object"));//獲取符合JDOM的Element對象Element root=(Element)value;//輸出到StringWriterXMLOutputter outputter=new XMLOutputter();//創(chuàng )建一個(gè)JDOM的XML輸出器StringWriter sw=new StringWriter();outputter.output(root,sw);//用支持W3C的DOM模型的Xerces解析器解析文本流DOMParser parser=new DOMParser();//創(chuàng )建一個(gè)DOM的XML解析器try {parser.parse(new org.xml.sax.InputSource(new java.io.StringReader(sw.toString())));}catch (Exception ex) {throw new java.io.IOException("序列化時(shí)產(chǎn)生錯誤");}//獲取符合DOM模型的Element對象org.w3c.dom.Element w3c_root =parser.getDocument().getDocumentElement();//放入序列化上下文對象中context.startElement(name, attributes);context.writeDOMElement(w3c_root);context.endElement();}JDomElementDeserializerFactory
反序列化器的工廠(chǎng)類(lèi)同序列化器的工廠(chǎng)類(lèi)一樣的設計,在此不在贅述。代碼:
package org.apache.axis.encoding.ser;public class JDomElementDeserializerFactoryextends BaseDeserializerFactory {public JDomElementDeserializerFactory() {super(JDomElementDeserializer.class);}}JDomElementDeserializer
用過(guò)SAX解析XML的讀者,對反序列化的實(shí)現比較容易理解,反序列化也采用了消息觸發(fā)的機制,我們只需繼承org.apache.axis.encoding.DeserializerImpl類(lèi),并覆蓋其中的onEndElement方法:
/*** 在元素結束觸發(fā)反序列化的方法* @param namespace String 命名空間* @param localName String 本地名稱(chēng)* @param context DeserializationContext 反序列化上下文* @throws SAXException*/public void onEndElement(String namespace, String localName,DeserializationContext context) throws SAXException {try {//從反序列化上下文對象中獲取原始的消息元素MessageElement msgElem = context.getCurElement();if (msgElem != null) {MessageContext messageContext = context.getMessageContext();Boolean currentElement = (Boolean) messageContext.getProperty(DESERIALIZE_CURRENT_ELEMENT);//如果當前的消息元素本身需要反序列化if (currentElement != null && currentElement.booleanValue()) {org.w3c.dom.Element element = msgElem.getAsDOM();org.jdom.input.DOMBuilder db=new org.jdom.input.DOMBuilder();value=db.build(element);messageContext.setProperty(DESERIALIZE_CURRENT_ELEMENT,Boolean.FALSE);return;}//反序列化消息元素中的消息體java.util.ArrayList children = msgElem.getChildren();if (children != null) {//取得消息體msgElem = (MessageElement) children.get(0);if (msgElem != null) {org.w3c.dom.Element ret = msgElem.getAsDOM();org.jdom.input.DOMBuilder db=new org.jdom.input.DOMBuilder();//用DOMBuilder將DOM模型的Element,轉換成JDOM模型的Elementvalue=db.build(ret);}}}}catch (Exception ex) {//錯誤,則記日志,并拋SAXExceptionlog.error(Messages.getMessage("exception00"), ex);throw new SAXException(ex);}}完成這四個(gè)類(lèi)的編碼,序列化/反序列化器的開(kāi)發(fā)工作基本完成,下面將詳細講解使用及部署方法。
服務(wù)器端應用篇
為了簡(jiǎn)單起見(jiàn),我們將一個(gè)很簡(jiǎn)單的類(lèi)通過(guò)Web服務(wù)發(fā)布,類(lèi)中只有一個(gè)名稱(chēng)為hello函數,函數的返回值為JDOM模型的Element。代碼如下:
package test;import org.jdom.*;import java.rmi.RemoteException;public class Sample1 implements java.rmi.Remote{public Sample1() {}public Element hello(String name){Element root=new Element("root");Element hello=new Element("hello");hello.setText("hello,"+name+"!");root.addContent(hello);return root;}}關(guān)于如何將一個(gè)類(lèi)發(fā)布成Web服務(wù),在此并不進(jìn)行介紹,相信讀者可以自己完成,我們只關(guān)注如何將序列化/反序列化器加入到我們的Web服務(wù)中。打開(kāi)web服務(wù)的配置文件server-config.xml,編輯關(guān)于Sample1的服務(wù)的配置部分:
<service name="Sample1" type="" regenerateElement="true"provider="java:RPC" style="rpc" use="encoded"><parameter name="scope" value="Request" regenerateElement="false"/><parameter name="className" value="test.Sample1" regenerateElement="false"/><parameter name="allowedMethods" value="*" regenerateElement="false"/><typeMappingencodingStyle="http://schemas.xmlsoap.org/soap/encoding/"qname="ns1:Element"languageSpecificType="java:org.jdom.Element"serializer="org.apache.axis.encoding.ser.JDomElementSerializerFactory"deserializer="org.apache.axis.encoding.ser.JDomElementDeserializerFactory"name="Element" regenerateElement="true"xmlns:ns1="http://jdom.org"/></service>
注意上面代碼中的粗體字部分,是我們現在要添加的,它表述了如何將序列化反序列化器部署到Web服務(wù)中。
部署到Web Server
解壓縮本文后面附帶的源代碼,根目錄下有build.xml文件,讀者需要正確安裝配置好Apache Ant,然后運行
Ant make
編譯后可生成壓縮文件sample.war。將生成的war包部署到Tomcat4.1下,啟動(dòng)Tomcat,本文默認的Tomcat監聽(tīng)的http端口為8080。后面的客戶(hù)端測試程序也將通過(guò)連接這一端口訪(fǎng)問(wèn)此Web服務(wù)。如果讀者的Tomcat不在8080端口上工作,那么客戶(hù)端程序也要進(jìn)行相應的修改。最后啟動(dòng)Tomcat,這部分操作完成。
客戶(hù)端應用篇
下面我們將編寫(xiě)客戶(hù)端程序訪(fǎng)問(wèn)剛才部署的Web服務(wù),講解如何把我們編寫(xiě)的序列化/反序列化器加載到客戶(hù)端應用程序中,下面是客戶(hù)端調用的代碼,注意斜體字部分,是關(guān)于序列化/反序列化器的注冊過(guò)程(如果你的Web服務(wù)器不是工作在8080端口,或采用了其他Web服務(wù)名,請自行更改下面程序中的url變量中的值),我們在test包下創(chuàng )建了一個(gè)名稱(chēng)為 Client的類(lèi),代碼如下:
package test;import org.apache.axis.client.Service;import org.apache.axis.client.Call;import org.apache.axis.utils.Options;import javax.xml.namespace.QName;public class Client {public Client() {}public static void main(String[] args) throws Exception{if(args.length<1){System.out.println("錯誤:缺少參數");System.exit(0);}//Web服務(wù)的URLString url="http://localhost:8080/sample/services/Sample1";Service service=new Service();Call call = (Call)service.createCall();call.setTargetEndpointAddress(url);//注冊序列化/反序列化器call.registerTypeMapping(org.jdom.Element.class,new QName("http://jdom.org","Element"),new org.apache.axis.encoding.ser.JDomElementSerializerFactory(),new org.apache.axis.encoding.ser.JDomElementDeserializerFactory());//設置調用方法call.setOperationName(new javax.xml.namespace.QName("http://test", "hello"));//Web服務(wù)調用java.lang.Object _resp = call.invoke(new java.lang.Object[] {args[0]});//輸出到屏幕org.jdom.output.XMLOutputter out=new org.jdom.output.XMLOutputter();out.output( (org.jdom.Element) _resp, System.out);}}編譯后運行該程序,在控制臺窗口工程的根目錄下輸入
run world ( 其中"world"為調用例程中API的入口參數)
經(jīng)過(guò)一次web通訊,一兩秒后屏幕將顯示運行結果:
<root><hello>hello,world!</hello></root>
至此我們完成了一次Web服務(wù)的訪(fǎng)問(wèn)過(guò)程。如果在程序執行過(guò)程中,我們用TCP Moniter之類(lèi)的工具監視這一次訪(fǎng)問(wèn)中的在網(wǎng)絡(luò )中流入流出的數據,可以看到客戶(hù)端發(fā)起調用的xml數據流如下:
POST /sample/services/Sample1 HTTP/1.0Content-Type: text/xml; charset=utf-8Accept: application/soap+xml,application/dime, multipart/related, text/*User-Agent: Axis/1.1Host: 127.0.0.1Cache-Control: no-cachePragma: no-cacheSOAPAction: ""Content-Length: 430<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><ns1:hello soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"xmlns:ns1="http://test"><name xsi:type="xsd:string">world</name></ns1:hello></soapenv:Body></soapenv:Envelope>
服務(wù)器端返回的結果的XML輸出流如下:
HTTP/1.1 200 OKContent-Type: text/xml; charset=utf-8Date: Wed, 31 Mar 2004 06:42:18 GMTServer: Apache Coyote/1.0Connection: close<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv"http://schemas.xmlsoap.org/soap/envelope/"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><ns1:helloResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"xmlns:ns1="http://test"><ns1:helloReturn href="#id0"/></ns1:helloResponse><multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"xsi:type="ns2:Element"xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"xmlns:ns2="http://jdom.org"><root><hello>hello,world!</hello></root></multiRef></soapenv:Body></soapenv:Envelope>
結語(yǔ)
以上詳細講解了Axis的序列化/反序列化器的開(kāi)發(fā)過(guò)程,相信讀者已經(jīng)從中學(xué)到了不少知識,并能夠應用于自己的項目開(kāi)發(fā)中去。通過(guò)掌握這一技術(shù),我們將更為深刻的理解Axis的內部結構和Web服務(wù)的工作機理,這些經(jīng)驗是市面上那些泛泛的講解JAVA Web服務(wù)的參考書(shū)上所學(xué)不到的。后續的文章還將向您展示一些在Java Web服務(wù)深度開(kāi)發(fā)中的高級技術(shù),讓您真正駕馭Axis。
參考資料
聯(lián)系客服