三、带有javabean的helloworld实例
如前所述,apache soap提供了许多预先构造的串行化和反串行化方法,其中包括为利用java vector、enumeration、数组、javabean作为参数和返回值而提供的串行化器和反串行化器。在这一部分,我将修改helloworld服务,通过一个javabean传入接收hello信息的用户名。
3.1、helloworld服务
改写后的helloworld服务完整代码如下:
|
package hello; public class helloserver { public string sayhelloto(string name) { system.out.println("sayhelloto(string name)"); return "hello " + name + ", how are you doing?"; } public string sayhelloto(name thename) { system.out.println("sayhelloto(name thename)"); return "hello " + thename.getname() + ", how are you doing?"; } } |
服务的代码仍旧很简单,仍旧类似于不用javabean时的helloworld服务。不过,这意味着最复杂的工作都转移到了客户端。事实上,这个版本的服务与以前版本的唯一差别在于,现在出现了一个重载的sayhelloto()方法。上面的代码中重载后的方法用粗体字显示。
重载的方法需要一个对name javabean的引用。name javabean的定义如下:
|
package hello; public class name { private string name; public string getname() { return name; } public void setname(string name) { this.name = name; } } |
3.2、部署服务
部署一个使用了javabean的服务时,需要为apache soap服务器提供一些额外的信息。因此,现在部署服务的过程稍微复杂一点。
■ 使用管理工具部署服务
要使用管理工具部署这个新版的helloworld服务,首先按照前面所介绍的步骤进行,但这一次不要点击deploy按钮。现在,在number of mappings输入框输入1,它表示我们将给出一个映射(即name javabean)的信息。紧接mappings之下有一个表格,我们要用到这个表格的第一行。保留encoding style的值为soap,把namespace uri设置成对象的id:在本例中,它是urn:hello。接下来,把local part和java type输入框设置成name javabean的完整名字,即hello.name。最后,把java to xml serializer和xml to java deserializer输入框设置成org.apache.soap.encoding.soapenc.beanserializer,这是一个实现了serializer和deserializer接口的类,用来串行化和反串行化name javabean。如果你用到了更多的javabean(比如还有一个address bean),则应该在这个表格中输入其他bean的信息,同时还应该更新number of mappings输入框的值,使之反映出表格中实际被使用的行数。
■ 从命令行部署服务
要从命令行进行部署,我们只需修改作为命令行参数传入的xml部署描述器文件。修改后的xml文件如下所示:
|
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:hello"> <isd:provider type="java" scope="application" methods="sayhelloto"> <isd:java class="hello.helloserver" static="false"/> </isd:provider> <isd:mappings> <isd:map encodingstyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:x="urn:hello" qname="x:hello.name" javatype="hello.name" java2xmlclassname="org.apache.soap.encoding.soapenc.beanserializer" xml2javaclassname="org.apache.soap.encoding.soapenc.beanserializer"/> </isd:mappings> </isd:service> |
正如在前一个例子中,这些xml代码所包含的信息和通过web界面的管理工具所提供的信息一样。
3.3、helloworld客户程序
和第一个例子一样,客户程序更复杂,也更令人感兴趣。这里我不再仔细分析整个客户程序,而是介绍两个客户程序版本的不同之处。由于调用方法的一个参数(在本例中,它是唯一的参数)是一个javabean,所以必须手工设置一个类型映射注册项。这个任务通过如下步骤完成:先创建org.apache.soap.encoding.soapmappingregistry类的一个实例,然后调用它的maptypes()方法。正如maptypes()方法名字所预示的,它用来注册一个以前未知的类型,比如定制的javabean。maptypes()方法的参数包括要使用的编码方式、限定的javabean名字、类型的完整类名、串行化器和反串行化器。在本例中,执行串行化任务的是标准的bean串行化器。限定的javabean名字包含一个元素的名字,包括它所属的名称空间。在本例中,name javabean的限定名字由名称空间uri(urn:hello)和本地名字(hello.name)结合构成。请看下面的代码片断:
|
// 创建类型映射注册器 soapmappingregistry smr = new soapmappingregistry(); beanserializer beanser = new beanserializer(); // 映射类型 smr.maptypes(constants.ns_uri_soap_enc, new qname("urn:hello", "hello.name"),hello.name.class, beanser, beanser); |
接下来,客户程序必须告诉call对象使用新的注册器而不是默认的注册器。为此,我们要调用call对象的setsoapmappingregistry()方法,如下所示:
| call.setsoapmappingregistry(smr); |
手工设置好类型映射注册器之后,接下来还必须为call对象设置参数。这一步骤可以按前面介绍的方法完成,不同之处在于,现在我们不再用字符串类型的名字作为参数,而是用javabean作为参数,如下所示:
|
// 设置调用参数 vector params = new vector(); name thename = new name(); thename.setname(name); params.addelement(new parameter("name", hello.name.class, thename, null)); call.setparams(params); |
客户程序剩下的部分和原来的版本一样。listing 3显示了完整的客户程序代码:
|
listing 3: client2.java package hello; import java.net.url; import java.util.vector; import org.apache.soap.soapexception; import org.apache.soap.constants; import org.apache.soap.fault; import org.apache.soap.rpc.call; import org.apache.soap.rpc.parameter; import org.apache.soap.rpc.response; import org.apache.soap.encoding.soapmappingregistry; import org.apache.soap.encoding.soapenc.beanserializer; import org.apache.soap.util.xml.qname; public class client2 // 检查应答 |
四、编译和运行程序
现在整个程序的开发工作已经完成,该是运行它的时候了。不过,我们首先要编译服务程序和客户程序。
创建一个hello目录,把client1.java、client2.java和helloserver.java复制到这个目录。我把hello目录放到了apache soap的示例目录(即e:\soap-2_0\samples)之下。编译程序时,classpath中只需包含hello目录的父目录(即e:\soap-2_0\samples)、soap.jar和xerces.jar。我用下面的批命令编译程序:
|
set classpath=e:\soap-2_0\samples\;e:\soap-2_0\lib\soap.jar;e:\xerces-1_2_0\xerces.jar javac -d .. helloserver.java client.java client2.java |
注意:从hello目录执行这个批命令文件。
要使用这个服务,除了部署它之外,还需要修改web服务器的classpath,确保web服务能够找到hello.helloserver类——对于本例,这是指把e:\soap-2_0\samples加入到web服务器的classpath。对classpath进行必要的修改之后,重新启动web服务器。接下来就可以运行客户程序了。下面是我运行hello.client的批命令文件:
| set classpath=e:\soap-2_0\samples\;e:\soap-2_0\lib\soap.jar;e:\xerces-1_2_0\xerces.jar java hello.client tarak |
这里的classpath和编译程序时用的classpath相同。
最后,运行hello.client2的批命令文件可以如下:
| set classpath=e:\soap-2_0\samples\;e:\soap-2_0\lib\soap.jar;e:\xerces-1_2_0\xerces.jar java hello.client2 tarak |
观察web服务器的控制台窗口,看看在运行两个不同的客户程序时,helloworld服务的哪些方法正在被调用。
■ 结束语
在这篇文章中,我介绍了如何用apache soap实现来创建简单的基于soap的服务。在soap实现方面,另一个重要的竞争者是microsoft。遗憾的是,“纯”java开发者在使用microsoft实现的时候会有一段艰苦的时光,因为它的实现包含了com对象。
在下一篇文章中,我将介绍apache soap支持的另一种创建服务的方式:使用javascript之类的脚本语言,而不是java。另外,我还要介绍一个很不错的javascript引擎,即rhino。
■ 参考资源
- w3c的soap 1.1规范:
http://www.w3.org/tr/soap/ - 下载apache soap:
http://xml.apache.org/dist/soap/ - 有关ibm soap工程的更多信息:
http://www.alphaworks.ibm.com/tech/soap4j - apache soap可用功能的一个清单:
http://xml.apache.org/soap/features.html - apache许可协议:
http://www.apache.org/license.txt - 下载tomcat 3.1
http://jakarta.apache.org/builds/jakarta-tomcat/release/v3.1.1/bin/ - 下载apache xerces 1.2版本:
http://xml.apache.org/dist/xerces-j/ - "ms soap sdk vs ibm soap4j: comparison & review," james snell (oreilly):
http://windows.oreilly.com/news/soapreview_0600.html
