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。工具 |
kSOAP2.0还有一个优势是,改进了对Microsoft dotNET的兼容。之前有不少人抱怨kSOAP调用dotNET编写的Web Service时遇到了很多的困扰。
本章节咱们将使用kSOAP 2.0的例子来说解。
为了使用kSOAP 2.0,必须还要下载工具包kXML2。
下载提示: kXML当前有两个版本:1.21和2.0。 |
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之间,既能够传递简单的数值,也能够传递各类各样的类对象