Web Servicek引擎kSOAP的使用教程
1.概述
對于J2ME訪問遠端的web Service,除了官方標準JSR 172,我們還有兩種選擇:
l kSOAP
l Wingfoot
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更小更快。
2.kSOAP2接口
讓我們先熟悉一下即將用到的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解析的:
2.1.kSOAP和Web 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();
}
2.2.webservice返回復雜描述的情況
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。
2.3.webservice傳遞自定義復雜對象
下面我們講述如何在MIDP設備和webservice之間傳遞自定義類,比如這個類中不但有String類型成員變量,還有Vector之類的復雜類型。
大致思路就是,在服務器端將類實例按照一定規格(一個一個的成員變量寫)序列化為byte[],將這個byte[]數組返回給kSOAP2。kSOAP2收到之后,再反序列化,將byte[]一段一段地讀入類實例。
2.3.1.webservice服務器端的做法
我們先來定義要傳遞的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]);
}
}
…
}
2.3.2.MIDP設備的做法
和前面的MIDlet代碼差不多,只不過要kSOAP2的MarshalBase64出場了。
在kSOAP中,我們用Base64把二進制流編碼為ASCII字符串,這樣就可以通過XML/SOAP傳輸二進制數據了。
org.ksoap2.serialization.MarshalBase64的目的就是,把SOAP XML中的xsd:based64Binary元素序列化為Java字節數組(byete array)類型。類似的,kSOAP2還提供了MarshalDate、MarshalHashtable類來把相應的元素序列化為Java的Date、Hashtable類型。
使用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之間,既可以傳遞簡單的數值,也可以傳遞各種各樣的類對象。
轉自:http://www.cnblogs.com/ice4c/archive/2010/12/17.html