Web Service深度編程——Axis序列化/反序列化器開發指南

作者簡介

薛穀雨是NORDSAN(北京)信息科技開發有限公司高級JAVA研發工程師,正致力於企業級異構數據交換的服務器產品的研發,在J2EE和WEB SERVICE方面有較爲豐富的開發經驗,你可以通過 [email protected] 與他取得聯繫。

前言

Axis是Apache組織推出的SOAP引擎,Axis項目是Apache組織著名的SOAP項目的後繼項目。Axis爲開發者提供了大量的序列化/反序列化器,能夠基本滿足大部分應用。但在某些情況下,對特定的對象,現有的序列化/反序列化器不能勝任,於是只有開發人員自己實現專用於此對象的序列化/反序列化器插入到Axis中來完成序列化工作。考慮到Web Service是一門新興技術,中文資料大多是泛泛的講解,關於序列化/反序列化器的開發鮮有較爲深入的介紹,本文提供一份較爲完整的開發指南,並提供了一個十分有用的實現,即序列化JDOM模型的Element,使其可以通過Web 服務在網絡上傳輸,我想這一擴展是許多采用JDOM作爲XML解析工具的開發人員都夢寐以求的功能。通過本文的介紹和實例,希望能起到拋磚引玉的作用,讀者在閱讀完本文之後可以輕鬆的實現針對於任何非符合BEAN規範的對象的序列化/反序列化器。

本文所面對的讀者需要有一定的使用Axis做Web服務開發的開發經驗,因此關於如何Axis的基礎知識並不在本文的介紹範圍,如果讀者對此感興趣,可以參考本文最後的參考資料部分,去相應的網站進行學習。

序列化/反序列化器簡介

序列化/反序列化器在英文中的對應翻譯是Serializer/Deserializer,一個序列化器的功能是遵循一定的映射規則和編碼風格,將一種類型的JAVA對象通過某種特定的機制,轉換成爲XML描述的形式;反序列化器的功能是序列化器所做工作的逆操作,兩者相輔相成,成對出現。Axis中的序列化/反序列化器採用設計範式中的工廠模式,每一個Serializer唯一對應一個SerializerFactory;每一個Deserializer唯一對應一個DeserializerFactory。一種類型的JAVA對象具體要採用哪個序列化/反序列化器需要在提供Web服務的服務器和調用Web服務的客戶端分別配置,關於這一部分如何配置,我將在本文後面的內容中進行詳細介紹。Axis已經爲開發者提供了豐富的序列化/反序列化器,對於java的基本數據類型,絕大部分常用的容器類(比如數組類型,Vector類型等)都提供了實現,特別是提供了對W3C的DOM對象(比如Document, Element等)和符合Bean規範的JAVA對象提供了功能完善的序列化/反序列化器,因此我們在需要的時候只要在配置文件中配置一下就可以直接使用。如果對象中包含其它類型的對象,比如Vector中包含一組Bean對象,Axis會自動疊代的調用序列化器,最終拼裝成唯一的XML表述。在還原成JAVA對象時,也遵循這樣的疊代操作逆向進行。關於Axis到底內置了哪些序列化/反序列化器,您可以參照Axis的API文檔中包org.apache.axis.encoding.ser下的類的名稱"望文生義"的瞭解一下,在以後的開發中做到心中有數。但對於一些特殊類型的對象(其實我們自己開發的很大一部分類都是這種特殊類型的對象,很少有絕對符合Bean規範的),需要通過Web服務進行傳遞,我們不得不開發自己的序列化/反序列化器。

開發篇

開發自己的序列化/反序列化器是一個激動人心的工作,但是卻並不複雜,需要做的事情包括實現名成爲org.apache.axis.encoding的包中的SerializerFactory,Serializer,DeserializerFactory和Deserializer這四個接口。下面我將結合一個實例來講解序列化/反序列化器的開發方法,希望讀者能夠一邊參看本文提供的源代碼一邊學習。

JDOM作爲一款比較"另類"的XML解析工具(因爲它不符合W3C的DOM模型,自己另立一套)默默地佔領着java世界裏的xml解析器的半壁江山,由於其簡潔的設計和方便靈活的API調用,已經漸漸成爲了許多開發人員在進行XML開發的首選。但是Axis是建立在W3C的DOM模型的基礎之上,師出名們正派,自然不屑與JDOM爲伍。因此當開發人員想將自己已經寫好的基於JDOM的應用模塊採用Web服務的方式發佈的時候,不可避免的會遇到如何將JDOM模型下的對象如Document, Element等序列化的問題。在軟件工程師不會自己擴展Axis的序列化/反序列化器的時候,我們只能有兩個辦法達到這個目的,第一個就是更改以前應用模塊內的API設計,使暴露的入口參數和返回值參數都是W3C的對象類型,但這種做法並不現實,因爲這一應用模塊往往不是獨立存在,牽一髮將動全身,導致舊有系統架構的崩塌;另一種做法就是爲這個模塊做一個代理類,它做的工作就對外接收或返回DOM模型的對象,對內轉換成JDOM模型的對象,然後轉發給應用模塊,繁瑣且效率低下。當我們向Axis注入了針對於JDOM模型的序列化/反序列化器後,這一工作便可以由Axis代勞了。下面我們將逐個開發這四個類:

JDomElementSerializerFactory

JDomElementSerializerFactory是一個工廠類,需要通過某種機制註冊到Axis引擎(具體方法見下面"服務器端應用篇");Axis通過調用它,來實例化JDomElementSerializer。Axis 提供了BaseSerializerFactory,這個類是一個抽象類,並實現其中包含了一些可重用的代碼。我們自己開發的工廠類只需簡單繼承這個類就可以。構造函數中需要調用父類的構造函數將序列器類下面是它的源代碼:

package org.apache.axis.encoding.ser;

public class JDomElementSerializerFactory 
  extends BaseSerializerFactory {

  public JDomElementSerializerFactory() {
    super(JDomElementSerializer.class);
  }
}

JDomElementSerializer

JDomElementSerializer實現org.apache.axis.encoding.Serializer接口,其核心API是serialize(),我們需要在這個方法的內部完成對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;

    //輸出到StringWriter
    XMLOutputter outputter=new XMLOutputter();//創建一個JDOM的XML輸出器
    StringWriter sw=new StringWriter();
    outputter.output(root,sw);

    //用支持W3C的DOM模型的Xerces解析器解析文本流
    DOMParser parser=new DOMParser();//創建一個DOM的XML解析器
    try {
      parser.parse(new org.xml.sax.InputSource(
	    new java.io.StringReader(sw.toString())));
    }catch (Exception ex) {
      throw new java.io.IOException("序列化時產生錯誤");
    }

    //獲取符合DOM模型的Element對象
    org.w3c.dom.Element w3c_root =
	  parser.getDocument().getDocumentElement();

    //放入序列化上下文對象中
    context.startElement(name, attributes);
    context.writeDOMElement(w3c_root);
    context.endElement();
  }

JDomElementDeserializerFactory

反序列化器的工廠類同序列化器的工廠類一樣的設計,在此不在贅述。代碼:

package org.apache.axis.encoding.ser;

public class JDomElementDeserializerFactory 
  extends BaseDeserializerFactory {

  public JDomElementDeserializerFactory() {
    super(JDomElementDeserializer.class);
  }

}

JDomElementDeserializer

用過SAX解析XML的讀者,對反序列化的實現比較容易理解,反序列化也採用了消息觸發的機制,我們只需繼承org.apache.axis.encoding.DeserializerImpl類,並覆蓋其中的onEndElement方法:

/**
   * 在元素結束觸發反序列化的方法
   * @param namespace String 命名空間
   * @param localName String 本地名稱
   * @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模型的Element
            value=db.build(ret);
          }
        }

      }
    }
    catch (Exception ex) {
      //錯誤,則記日誌,並拋SAXException
      log.error(Messages.getMessage("exception00"), ex);
      throw new SAXException(ex);
    }
  }

完成這四個類的編碼,序列化/反序列化器的開發工作基本完成,下面將詳細講解使用及部署方法。

服務器端應用篇

爲了簡單起見,我們將一個很簡單的類通過Web服務發佈,類中只有一個名稱爲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;
  }

}

關於如何將一個類發佈成Web服務,在此並不進行介紹,相信讀者可以自己完成,我們只關注如何將序列化/反序列化器加入到我們的Web服務中。打開web服務的配置文件server-config.xml,編輯關於Sample1的服務的配置部分:

<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"/>

<typeMapping
  encodingStyle="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服務中。

部署到Web Server

解壓縮本文後面附帶的源代碼,根目錄下有build.xml文件,讀者需要正確安裝配置好Apache Ant,然後運行

Ant make

編譯後可生成壓縮文件sample.war。將生成的war包部署到Tomcat4.1下,啓動Tomcat,本文默認的Tomcat監聽的http端口爲8080。後面的客戶端測試程序也將通過連接這一端口訪問此Web服務。如果讀者的Tomcat不在8080端口上工作,那麼客戶端程序也要進行相應的修改。最後啓動Tomcat,這部分操作完成。

客戶端應用篇

下面我們將編寫客戶端程序訪問剛纔部署的Web服務,講解如何把我們編寫的序列化/反序列化器加載到客戶端應用程序中,下面是客戶端調用的代碼,注意斜體字部分,是關於序列化/反序列化器的註冊過程(如果你的Web服務器不是工作在8080端口,或採用了其他Web服務名,請自行更改下面程序中的url變量中的值),我們在test包下創建了一個名稱爲 Client的類,代碼如下:

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服務的URL
    String 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服務調用
    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的入口參數)

經過一次web通訊,一兩秒後屏幕將顯示運行結果:

<root>
    <hello>hello,world!</hello>
</root>

至此我們完成了一次Web服務的訪問過程。如果在程序執行過程中,我們用TCP Moniter之類的工具監視這一次訪問中的在網絡中流入流出的數據,可以看到客戶端發起調用的xml數據流如下:

POST /sample/services/Sample1 HTTP/1.0
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, 
application/dime, multipart/related, text/*
User-Agent: Axis/1.1
Host: 127.0.0.1
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: ""
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>

服務器端返回的結果的XML輸出流如下:

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Date: Wed, 31 Mar 2004 06:42:18 GMT
Server: Apache Coyote/1.0
Connection: 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>

結語

以上詳細講解了Axis的序列化/反序列化器的開發過程,相信讀者已經從中學到了不少知識,並能夠應用於自己的項目開發中去。通過掌握這一技術,我們將更爲深刻的理解Axis的內部結構和Web服務的工作機理,這些經驗是市面上那些泛泛的講解JAVA Web服務的參考書上所學不到的。後續的文章還將向您展示一些在Java Web服務深度開發中的高級技術,讓您真正駕馭Axis。

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章