ksoap2- webservice

1.概述html

對於J2ME訪問遠端的Web Service,除了官方標準JSR 172,咱們還有兩種選擇:java

l         kSOAPweb

l         Wingfootapi

Wingfoot是由Wingfoot Software(www.wingfoot.com)出品的一款J2ME(CLDC/CDC) SOAP1.1的輕量級實現方案。數組

kSOAP是Enhydra.org的一個開源做品,是EnhydraME項目的一部分。基於Enhydra.org出品的開源通用XML解析器kXML,kSOAP完成了J2ME/MIDP平臺上的SOAP解析和調用工做。服務器

Stefan Haustein領導的kSOAP開發小組於2001年5月17日推出了Alhpa版本。以後又通過了一年的開發,2002年6月6日推出的kSOAP 1.2支持了SOAP1.2規範。2003年8月25日推出的kSOAP2,對SOAP序列化規範支持得更好了。網絡

大多數人選擇kSOAP的緣由是,kSOAP雖然在2003年8月以後就再也不維護了,但它是Open Source的,很容易加入加強特性,好比說默認狀況下kSOAP2僅僅支持cmnet接入點,能夠修改kSOAP2的HttpTransport.java代碼增長對cmwap接入點的支持。框架

下載提示函數

kSOAP當前有兩個版本:1.2和2.0。工具

官方網站:http://ksoap.objectweb.org/

kSOAP2.0還有一個優勢是,改進了對Microsoft dotNET的兼容。之前有不少人抱怨kSOAP調用dotNET編寫的Web Service時遇到了很多的困擾。

本章節咱們將使用kSOAP 2.0的例子來說解。

爲了使用kSOAP 2.0,必須還要下載工具包kXML2。

下載提示

kXML當前有兩個版本:1.21和2.0。

官方網站:http://kxml.objectweb.org/

kXML2比kXML更小更快。

2kSOAP2接口

讓咱們先熟悉一下即將用到的kSOAP2的經常使用接口。

接口

org.kSOAP2. SoapEnvelope

org.kSOAP2. SoapSerializationEnvelope

org.kSOAP2. SoapObject

org.kSOAP2.transport. HttpTransport

SoapEnvelope對應於SOAP規範中的SOAP Envelope,封裝了head和body對象。

SoapSerializationEnvelope是kSOAP2新增長的類,是對SoapEnvelope的擴展,對SOAP序列化(Serialization)格式規範提供了支持,可以對簡單對象自動進行序列化(simple object serialization)。而kSOAP1.x則是經過org.kSOAP.ClassMap來作序列化的,不太好操做,也不利於擴展。

SoapObject讓你自如地構造SOAP調用;

HttpTransport爲你屏蔽了Internet訪問/請求和獲取服務器SOAP的細節。

 

下面咱們經過一個最簡單的webservice調用,來看看kSOAP是如何作到SOAP解析的:

21kSOAPWeb Service之間傳遞String

webservice傳遞String給MIDP是一件很簡單的事情。首先在服務器端,無論你是用Microsft ASP.NET建立webservice,仍是由Tomcat+AXIS1.2支撐的webservice,均可以這麼編寫主服務類:

服務器端

public class SimplekSOAPWS {

   

    public SimplekSOAPWS () {

    }

   

    public String  foo(String username, String password) {

        return 「fooResult」;

}

}

kSOAP是如何調用這個webservice的呢?

首先要使用SoapObject,這是一個高度抽象化的類,完成SOAP調用。能夠調用它的addProperty方法填寫要調用的webservice方法的參數。以下面代碼所示:

SoapObject request  = new SoapObject(serviceNamespace, methodName);

SoapObject構造函數的兩個參數含義爲:

serviceNamespace – 你的webservice的命名空間,既能夠是

http://localhost:8088/flickrBuddy/services/Buddycast這樣的,也能夠是

urn:PI/DevCentral/SoapService這樣的;

methodName – 你要調用方法的名字。

而後,按照webservice方法參數的順序,依次調用

request.addProperty( "username", "user" );

request.addProperty( "password", "pass" );

來填充webservice參數。

注意

建議webservice的方法傳遞的參數儘可能用string類型。即便是int類型,kSOAP2與Java編寫的webservice也有可能交互發生異常。

對於webservice方法返回String類型的狀況,還用不着開發者作序列化(Serialization)定製工做。

要點

kSOAP 1.X/2.0能夠自動把四種SOAP類型映射爲Java類型

SOAP type           Java type

xsd:int             java.lang.Integer

xsd:long            java.lang.Long

xsd:string         java.lang.String

xsd:boolean         java.lang.Boolean

除此以外,都須要開發者本身作類型映射。

而後要告訴SoapSerializationEnvelope把構造好的SoapObject封裝進去:

SoapSerializationEnvelope envelope =

new SoapSerializationEnvelope(SoapEnvelope.VER11);

envelope.bodyOut = request;

要點

你能夠經過SoapSerializationEnvelope或者SoapEnvelope的構造函數來指明你要用SOAP的哪個規範,能夠是如下幾種之一:

常量SoapEnvelope.VER10:對應於SOAP 1.0規範

常量SoapEnvelope.VER11:對應於SOAP 1.1規範

常量SoapEnvelope.VER12:對應於SOAP 1.2規範

這樣,不管要調用的webservice採用了哪個SOAP規範,你均可以輕鬆應對。

接下來就要聲明

HttpTransport tx = new HttpTransport(serviceURL);

ht.debug = true;

HttpTransport構造函數的參數含義爲:

serviceURL – 要投遞SOAP數據的目標地址,譬如說

http://soap.amazon.com/onca/soap3 。

HttpTransport是一個強大的輔助類,來完成Http-call transport process,它封裝了網絡請求的一切,你徹底不用考慮序列化消息。咱們經過設置它的debug屬性爲true來打開調試信息。

方法HttpTransport.call()本身就可以發送請求給服務器、接收服務器響應並序列化SOAP消息,以下所示:

ht.call(null, envelope);

HttpTransport的call方法的兩個參數含義爲:

soapAction – SOAP 規範定義了一個名爲 SOAPAction 的新 HTTP 標頭,全部 SOAP HTTP 請求(即便是空的)都必須包含該標頭。 SOAPAction 標頭旨在代表該消息的意圖。一般能夠置此參數爲null,這樣HttpTransport就會設置HTTP標頭SOAPAction爲空字符串。

Envelope – 就是前面咱們構造好的SoapSerializationEnvelope或SoapEnvelope對象。

注意

對於HttpTransport的處理上,kSOAP2和kSOAP1.2的寫法不同。

對於kSOAP 1.2,HttpTransport的構造函數是HttpTransport (String url, String soapAction),第二個參數soapAction能夠是要調用的webservice方法名。

kSOAP 2,構造函數是 HttpTransport(String url)。kSOAP2至關於把webservice方法名分離出去,徹底交給SoapObject去封裝,而HttpTransport僅僅負責把SoapEnvelope發送出去並接收響應,這樣更合理一些。

調用call方法是一個同步過程,須要等待它返回。

返回以後,就能夠調用SoapSerializationEnvelope的getResult方法來獲取結果了:

Object Response = envelope.getResult();

若是HttpTransport的debug屬性爲true,那麼此時就能夠經過

System.out.println("Response dump>>" + tx.responseDump);

打印出HttpTransport的調試信息。尤爲當前面call方法和getResult方法發生異常時,這個調試信息是很是有用的。

前面咱們的webservice方法因爲是返回string,因此獲得這個string值就很是簡單了:

String sResponse = (String)Response;

注意

因爲HttpTransport類其實是調用了HttpConnection做網絡鏈接,因此必須另起一個線程來專門作kSOAP工做,不然會堵塞操做。

綜上所述,J2ME客戶端的MIDlet按鍵事件函數這麼寫便可:

MIDlet codes

import org.kSOAP2.SoapEnvelope;

import org.kSOAP2.serialization.SoapObject;

import org.kSOAP2.serialization.SoapSerializationEnvelope;

import org.kSOAP2.transport.HttpTransport;

 

public void commandAction(Command c, Displayable s) {

        if (c == exitCommand)

        {

            destroyApp(false);

            notifyDestroyed();

        }

        if (c == connectCommand)

        {

                     // 匿名內部Thread,調用kSOAP2訪問遠程服務。

            Thread webserviceThread = new Thread()

            {

                            public void run(){                   

                            try

                            {

                     String serviceNamespace =

 "http://localhost:8080/SimpleWS/services/SimplekSOAPWS";

                     String methodName = "foo";

                     String serviceURL =

 "http://localhost:8080/SimpleWS/services/SimplekSOAPWS";

 

SoapObject request  =

new SoapObject(serviceNamespace, methodName);

request.addProperty( "username", "user" );

request.addProperty( "password", "pass" );

 

                     SoapSerializationEnvelope envelope =

                  new SoapSerializationEnvelope(SoapEnvelope.VER11);

               envelope.bodyOut = request;

 

              HttpTransport tx = new HttpTransport(serviceURL);

              ht.debug = true;

              ht.call(null, envelope);

              Object Response = envelope.getResult();

/*

              * 必要時打印出tx.responseDump來觀察soap是否正確工做

            */

System.out.println("dump>>" + tx.responseDump);

               String sResponse = (String)Response;                              

                            }

                   catch (Exception e) {

                                  e.printStackTrace ();

                   }

                     }

            };

            webserviceThread.start();

        }

 

22webservice返回複雜描述的狀況

kSOAP2處理webservice簡單的string類型返回值是很容易的。那麼如何處理像亞馬遜網上書店這種webservice返回的複雜描述呢?

kSOAP2自帶了一個例子來講明,下面咱們就講解一下。

關於亞馬遜的查詢書目的webservice,你能夠經過

http://soap.amazon.com/schemas3/AmazonWebServices.wsdl

來獲知定義。

咱們要關注的是它的關鍵詞查詢請求的方法,它的定義是:

<operation name="KeywordSearchRequest">

     <soap:operation soapAction="http://soap.amazon.com" />

 <input>

<soap:body use="encoded"

encodingStyle=http://schemas.xmlsoap.org/soap/encoding/ namespace="http://soap.amazon.com" />

     </input>

<output>

<soap:body use="encoded"

encodingStyle=http://schemas.xmlsoap.org/soap/encoding/ namespace="http://soap.amazon.com" />

        </output>

      </operation>

咱們提交對包含指定關鍵詞的書目查詢,若是查詢成功,將會返回一系列書名節點,每一本書都提供了做者、出版社、出版日期、價格等等信息。這些書名節點都在一個「Details」節點下。查詢結果的總數放在TotalResults節點。每頁10個結果,能夠經過查看TotalPages節點來肯定須要多少頁。

那麼,kSOAP2能夠很簡單地經過SoapObject的getProperty方法來獲得書詳細信息的節點,存儲入一個Vector對象中,以下所示:

HttpTransport ht = new HttpTransport("http://soap.amazon.com/onca/soap3");

ht.call(null, envelope);

SoapObject result = (SoapObject) envelope.getResult();

Vector resultVector = (Vector) result.getProperty("Details");

Vector對象中實際上仍是存儲了一組SoapObject對象,這裏的每個SoapObject對象對應於一本書的DOM對象。

那麼如何獲得每一本書的書名、價格呢?

for(int i = 0; i < resultVector.size(); i++){

       SoapObject detail = (SoapObject) resultVector.elementAt(i);

       System.out.println("書名>>"+(String) detail.getProperty("ProductName"));

System.out.println("日期>>"+(String) detail.getProperty("ReleaseDate"));

System.out.println("價格>>"+(String) detail.getProperty("ListPrice"));

}

這樣就能夠了。

須要注意的是,要測試這個工程,必須到亞馬遜的http://www.amazon.com/webservice 註冊獲取Access Key ID,也就是webservice方法中的「devtag」參數所須要的Developer-Tag。

23webservice傳遞自定義複雜對象

下面咱們講述如何在MIDP設備和webservice之間傳遞自定義類,好比這個類中不但有String類型成員變量,還有Vector之類的複雜類型。

大體思路就是,在服務器端將類實例按照必定規格(一個一個的成員變量寫)序列化爲byte[],將這個byte[]數組返回給kSOAP2。kSOAP2收到以後,再反序列化,將byte[]一段一段地讀入類實例。

231webservice服務器端的作法

咱們先來定義要傳遞的wsTeam類:

類定義

public class wsTeam{

private String wsReturnCode;

private String wsPersonCount;

public StringVector  wsvPersonName;

public byte[] serialize();

public static wsTeam deserialize(byte[] data) ;

}

其中,StringVector類是另一個自定義類,就是簡單地把String[]封裝了一下,便於操做。StringVector類定義在示範代碼中能夠找到。

服務器端主要是序列化,因此咱們來說講wsTeam的serialize()函數。

wsTeam的序列化函數

       public byte[] serialize() {

              ByteArrayOutputStream baos = new ByteArrayOutputStream();

              DataOutputStream dos = new DataOutputStream(baos);

 

              try

              {

                     dos.writeUTF(wsReturnCode);

                     dos.writeUTF(wsPersonCount);

                     wsvPersonName.writeObject(dos);

                     baos.close();

                     dos.close();

              }

              catch(Exception exc)

              {

                     exc.printStackTrace();

              }

 

              return baos.toByteArray();

       }

這樣,類實例就能夠把本身序列化爲byte[]數組。

那麼,webservice能夠這麼提供:

服務器端

public class SimplekSOAPWS {

   

    public SimplekSOAPWS () {

    }

   

    public byte[]  foo2(String username, String password) {

        wsTeam obj= new wsTeam ();

           return obj.serialize();

}

}

到了MIDP設備上,要可以從byte[]恢復出wsTeam類實例才行。

 

StringVector的序列化方法writeObject也很簡單,先寫入字符串數組的大小,而後再將每個元素寫入,以下所示:

StringVector的序列化

public class StringVector

{…

public synchronized void writeObject(java.io.DataOutputStream s)

       throws java.io.IOException 

       { 

              //     Write out array length

              s.writeInt(count); 

              //     Write out all elements in the proper order.  

              for (int i=0; i<count; i++)

              {

                     s.writeUTF(data[i]);

              }

       }

}

 

232MIDP設備的作法

和前面的MIDlet代碼差很少,只不過要kSOAP2的MarshalBase64出場了。

kSOAP中,咱們用Base64把二進制流編碼爲ASCII字符串,這樣就能夠經過XML/SOAP傳輸二進制數據了。

org.kSOAP2.serialization.MarshalBase64的目的就是,把SOAP XML中的xsd:based64Binary元素序列化爲Java字節數組(byete array)類型。相似的,kSOAP2還提供了MarshalDateMarshalHashtable類來把相應的元素序列化爲JavaDateHashtable類型。

 

使用MarshalBase64

import org.kSOAP2.serialization.MarshalBase64;

 

SoapObject request = new SoapObject(serviceNamespace, methodName );

 

SoapSerializationEnvelope envelope =

       new SoapSerializationEnvelope(SoapEnvelope.VER11);

envelope.bodyOut = request;

new MarshalBase64().register(envelope);

 

HttpTransport tx = new HttpTransport(serviceNamespace);

tx.debug = true;

 

tx.call(null, envelope);

Object Response = envelope.getResult();

將接收到的SoapObject強制轉換爲byte[]。

轉換

byte[] by = (byte[])Response;

System.out.println("succ convert!");

而後,再調用

反序列化

wsTeam wc = wsTeam.deserialize(by);

這樣,在無線設備上就獲得了wsTeam類實例了。

 

wsTeam的deserialize函數是這麼定義的:

wsTeam的反序列化函數

public class StringVector

{…

       public static wsTeam deserialize(byte[] data) {

              ByteArrayInputStream bais = new ByteArrayInputStream(data);

              DataInputStream dis = new DataInputStream(bais);

              wsTeam wc = new wsTeam();

             

              try

              {

                     wc.wsReturnCode = dis.readUTF();

                     wc.wsPersonCount = dis.readUTF();

             

                     wc. wsvPersonName.readObject(dis);

      

                     bais.close();

                     dis.close();

              }

              catch(Exception exc)

             

                     exc.printStackTrace();

              }

 

              return wc;

       }

…}

StringVector的反序列化方法readObject也很簡單,先讀入字符串數組的大小,就自行新建一個一樣大小的字符串數組,而後再將每個元素寫入這個數組,以下所示:

StringVector的反序列化

public class StringVector

{…

       public synchronized void readObject(java.io.DataInputStream s)

       throws java.io.IOException, ClassNotFoundException  

       {    

              //     Read in array length and allocate array  

              int arrayLength = s.readInt(); 

              data = new String[arrayLength]; 

              // 同步data的大小

              count = arrayLength;

              //     Read in all elements in the proper order. 

              for (int i=0; i<arrayLength; i++)

              {

                     data[i] = s.readUTF();

              }

       }…

}

 

經過上面的反序列化,咱們就能夠經過

for (int i=0; i<wc.wsvPersonName.size(); i++) {

       System.out.println("第" + i +"我的:" +

                     wc.wsvPersonName.getStringAt(i));

}

來打印MIDlet上收到的類對象中的StringVector成員變量了。

3.小結

利用kSOAP2提供的框架,你能夠在無線設備和Internet webservice之間,既能夠傳遞簡單的數值,也能夠傳遞各類各樣的類對象

相關文章
相關標籤/搜索