[J2ME/kSOAP]kSOAP的运用讲义[去年8月手稿]

系统 1768 0

去年 8月份的一份手稿,完整描述了我对j2me-kSOAP如何和服务器端的Web Service交互的经验和教训。本手稿已刊登在mingjava兄弟的新书中。

[j2me]kSOAP 的运用

编写者

日期

关键词

郑昀 @ultrapower

2006-8-24

J2me webservice 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 之间,既可以传递简单的数值,也可以传递各种各样的类对象。



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1485436


[J2ME/kSOAP]kSOAP的运用讲义[去年8月手稿]


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论