三、带有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文件如下所示:
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"/> 正如在前一个例子中,这些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 { public static void main(String[] args) throws Exception { if(args.length == 0) { System.err.println("Usage: java hello.Client [SOAP-router-URL] "); System.exit (1); } try { URL url = null; String name = null; if(args.length == 2) { url = new URL(args[0]); name = args[1]; } else { url = new URL("http://localhost:8080/apache-soap/servlet/rpcrouter"); name = args[0]; } // 构造调用对象 Call call = new Call(); call.setTargetObjectURI("urn:Hello"); call.setMethodName("sayHelloTo"); call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC); // 创建类型映射注册器 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.setSOAPMappingRegistry(smr); // 设置参数 Vector params = new Vector(); Name theName = new Name(); theName.setName(name); params.addElement(new Parameter("name", hello.Name.class, theName, null)); call.setParams(params); // 发出调用 Response resp = null; try { resp = call.invoke(url, ""); } catch( SOAPException e ) { System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " + e.getMessage()); System.exit(-1); } // 检查应答 if( !resp.generatedFault() ) { Parameter ret = resp.getReturnValue(); Object value = ret.getValue(); System.out.println(value); } else { Fault fault = resp.getFault(); System.err.println("Generated fault: "); System.out.println (" Fault Code = " + fault.getFaultCode()); System.out.println (" Fault String = " + fault.getFaultString()); } } catch(Exception e) { e.printStackTrace(); } } } 四、编译和运行程序 现在整个程序的开发工作已经完成,该是运行它的时候了。不过,我们首先要编译服务程序和客户程序。 创建一个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 (O'Reilly): http://windows.oreilly.com/news/soapreview_0600.HTML

