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

news/2023/12/9 16:17:40

去年 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的轻量级实现方案。

kSOAPEnhydra.org的一个开源作品,是EnhydraME项目的一部分。基于Enhydra.org出品的开源通用XML解析器kXMLkSOAP完成J2ME/MIDP平台上的SOAP解析和调用工作

Stefan Haustein领导的kSOAP开发小组于2001517日推出了Alhpa版本。之后又经过了一年的开发,200266日推出的kSOAP 1.2支持了SOAP1.2规范。2003825日推出的kSOAP2,对SOAP序列化规范支持得更好了。

大多数人选择kSOAP的原因是,kSOAP虽然在20038月之后就不再维护了,但它是Open Source的,很容易加入增强特性,比如说默认情况下kSOAP2仅仅支持cmnet接入点,可以修改kSOAP2HttpTransport.java代码增加对cmwap接入点的支持。

下载提示

kSOAP当前有两个版本:1.22.0

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

kSOAP2.0还有一个优点是,改进了对Microsoft dotNET的兼容。以前有很多人抱怨kSOAP调用dotNET编写的Web Service时遇到了不少的困扰。

本章节我们将使用kSOAP 2.0的例子来讲解。

为了使用kSOAP 2.0,必须还要下载工具包kXML2

下载提示

kXML当前有两个版本:1.212.0

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

kXML2kXML更小更快。

2kSOAP2接口

让我们先熟悉一下即将用到的kSOAP2的常用接口。

接口

org.ksoap2. SoapEnvelope

org.ksoap2. SoapSerializationEnvelope

org.ksoap2. SoapObject

org.ksoap2.transport. HttpTransport

SoapEnvelope对应于SOAP规范中的SOAP Envelope,封装了headbody对象。

SoapSerializationEnvelopekSOAP2新增加的类,是对SoapEnvelope的扩展,对SOAP序列化(Serialization)格式规范提供了支持,能够对简单对象自动进行序列化(simple object serialization)。而kSOAP1.x则是通过org.ksoap.ClassMap来做序列化的,不太好操作,也不利于扩展。

SoapObject让你自如地构造SOAP调用;

HttpTransport为你屏蔽了Internet访问/请求和获取服务器SOAP的细节。

 

下面我们通过一个最简单的webservice调用,来看看kSOAP是如何做到SOAP解析的:

21kSOAPWeb Service之间传递String

webservice传递StringMIDP是一件很简单的事情。首先在服务器端,不管你是用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类型,kSOAP2Java编写的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);

HttpTransportcall方法的两个参数含义为:

soapAction – SOAP 规范定义了一个名为 SOAPAction 的新 HTTP 标头,所有 SOAP HTTP 请求(即使是空的)都必须包含该标头。 SOAPAction 标头旨在表明该消息的意图。通常可以置此参数为null,这样HttpTransport就会设置HTTP标头SOAPAction为空字符串。

Envelope – 就是前面我们构造好的SoapSerializationEnvelopeSoapEnvelope对象。

注意

对于HttpTransport的处理上,kSOAP2kSOAP1.2的写法不一样。

对于kSOAP 1.2HttpTransport的构造函数是HttpTransport (String url, String soapAction),第二个参数soapAction可以是要调用的webservice方法名。

kSOAP 2,构造函数是 HttpTransport(String url)kSOAP2相当于把webservice方法名分离出去,完全交给SoapObject去封装,而HttpTransport仅仅负责把SoapEnvelope发送出去并接收响应,这样更合理一些。

调用call方法是一个同步过程,需要等待它返回。

返回之后,就可以调用SoapSerializationEnvelopegetResult方法来获取结果了:

Object Response = envelope.getResult();

如果HttpTransportdebug属性为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可以很简单地通过SoapObjectgetProperty方法来得到书详细信息的节点,存储入一个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[]数组返回给kSOAP2kSOAP2收到之后反序列化,将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类定义在示范代码中可以找到。

服务器端主要是序列化,所以我们来讲讲wsTeamserialize()函数。

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

              {

                     s.writeUTF(data[i]);

              }

       }

}

 

232MIDP设备的做法

和前面的MIDlet代码差不多,只不过要kSOAP2MarshalBase64出场了

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类实例了。

 

wsTeamdeserialize函数是这么定义的:

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

              {

                     data[i] = s.readUTF();

              }

       }…

}

 

通过上面的反序列化,我们就可以通过

for (int i=0; i

       System.out.println("" + i +"个人:" +

                     wc.wsvPersonName.getStringAt(i));

}

来打印MIDlet上收到的类对象中的StringVector成员变量了。

3.小结

利用kSOAP2提供的框架,你可以在无线设备和Internet webservice之间,既可以传递简单的数值,也可以传递各种各样的类对象。





http://www.niftyadmin.cn/n/3648284.html

相关文章

多元化专业化没有好坏之分

多元化风潮没有好坏之分专业化和多元化&#xff0c;就其本质而言&#xff0c;并不是一个问题。从现象上来说&#xff0c;既有专业化公司的成功案例&#xff0c;也有多元化公司的成功案例&#xff0c;所以以某一个个案来否定或肯定是否多元化&#xff0c;并没有多少说服力&#…

深入浅出 RPC详解

浅出篇 近几年的项目中&#xff0c;服务化和微服务化渐渐成为中大型分布式系统架构的主流方式&#xff0c;而 RPC 在其中扮演着关键的作用。在平时的日常开发中我们都在隐式或显式的使用 RPC&#xff0c;一些刚入行的程序员会感觉 RPC 比较神秘&#xff0c;而一些有多年使用 RP…

如何在Ubuntu 20.04上安装Django并设置开发环境

介绍 (Introduction) Django is a free and open-source web framework written in Python with its core principles being scalability, re-usability and rapid development. It is also known for its framework-level consistency and loose coupling, allowing for indiv…

[职场生存]细节和感觉[一]:细节

[职场生存]细节和感觉[一]&#xff1a;细节 zhengyun_ustc 200701全文链接&#xff1a; [职场生存]细节和感觉[一][职场生存]细节和感觉[二]&#xff1a;细节包括哪些部分&#xff1f;[职场生存]细节和感觉[三]&#xff1a;感觉刚刚进入软件行业的时候&#xff0c;…

如何在Ubuntu 20.04上安装Anaconda Python发行版

介绍 (Introduction) Anaconda is an open-source package manager, environment manager, and distribution of the Python and R programming languages. It is commonly used for data science, machine learning, large-scale data processing, scientific computing, and …

如何在Ubuntu 20.04上安装和保护Redis [快速入门]

介绍 (Introduction) Redis is an in-memory key-value store known for its flexibility, performance, and wide language support. This quickstart tutorial demonstrates how to install, configure, and secure Redis on an Ubuntu 20.04 server. Redis是一个内存键值存储…

[职场生存]细节和感觉[二]:细节包括哪些部分?

[职场生存]细节和感觉[二]&#xff1a;细节包括哪些部分&#xff1f; zhengyun_ustc 200701前文链接&#xff1a;[职场生存]细节和感觉[一]前面说了细节是一种技巧&#xff0c;并且展示了老外的细节之一。那么到底我所说的细节都包括哪些部分呢&#xff1f;简单地说&#xf…

Java单元测试之JUnit篇

单元测试是编写测试代码&#xff0c;应该准确、快速地保证程序基本模块的正确性。 好的单元测试的标准 JUnit是Java单元测试框架&#xff0c;已经在Eclipse中默认安装。 JUnit4 JUnit4通过注解的方式来识别测试方法。目前支持的主要注解有&#xff1a; BeforeClass 全局只会…

手语翻译系统_如何建立一个神经网络将手语翻译成英语

手语翻译系统The author selected Code Org to receive a donation as part of the Write for DOnations program. 作者选择Code Org接受捐赠&#xff0c;这是Write for DOnations计划的一部分。 介绍 (Introduction) Computer vision is a subfield of computer science that…

[职场生存]细节和感觉[三]:感觉

[职场生存]细节和感觉[三]&#xff1a;感觉 zhengyun_ustc 200701前文链接&#xff1a;[职场生存]细节和感觉[一][职场生存]细节和感觉[二]&#xff1a;细节包括哪些部分&#xff1f;下面我们来说第二点&#xff1a;感觉。我所说的这个感觉不是以前我曾经强调过的“在工作中…

配置管理 ansible_Ansible配置管理简介

配置管理 ansible介绍 (Introduction) Configuration management is the process of handling changes to a system in a way that assures integrity over time, typically involving tools and processes that facilitate automation and observability. Even though this co…

职场笔记[0702]:用户体验和创业

职场笔记[0702]&#xff1a;用户体验和创业“需要说明&#xff0c;用户体验不是震撼性创新&#xff0c;而是把1000个细节做好的关照。就是说&#xff0c;重点并不在于创造一些前所未有的功能&#xff0c;而是把一些此前未被注意的使用障碍消除。。”--为iPhone狂 By 张亮“就象…

如何在Ubuntu 18.04上设置代码服务器Cloud IDE平台

介绍 (Introduction) With developer tools moving to the cloud, creation and adoption of cloud IDE (Integrated Development Environment) platforms is growing. Cloud IDEs allow for real-time collaboration between developer teams to work in a unified developmen…

Rails到底能支撑多大的负载?靠多进程吗?

哈哈&#xff0c;程序员杂志的编辑们可要谨慎点&#xff0c;对岸的兄弟们也看这本杂志&#xff0c; 对RoR的数据可要check清楚。其实我也是看了那期程序员杂志才决定不用RoR的。lightyror.blogspot.com可对杂志里面提到的数字表示不满了&#xff1a;Rails 的 High Traffic 负载…

apache2.4 mpm_如何在Ubuntu 18.04上使用MPM事件和PHP-FPM配置Apache HTTP

apache2.4 mpmThe author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program. 作者选择了COVID-19救济基金来接受捐赠&#xff0c;这是Write for DOnations计划的一部分。 介绍 (Introduction) The Apache HTTP web serve…
最新文章