- 浏览: 262563 次
- 性别:
- 来自: 成都
文章分类
最新评论
-
cuqing:
整体思路还是正确的,请问1F何处有问题?我稍微看了下,没发现什 ...
JS的splice()方法和slice()方法 -
zhao_tuo:
总结的不错
[转] Java注释@interface的用法 -
cheychey:
好好看看你的代码的真确性,再贴出来。。
JS的splice()方法和slice()方法 -
cuqing:
--查询预受理申请审计表中按照业务类别分类的最新创建(最近修改 ...
Oracle分组查询后,获取最大值的记录 -
cuqing:
这种方式足够既能直接分组,又能筛选分组中的最大Id,足够用了。 ...
Oracle分组查询后,获取最大值的记录
[转] Apache SOAP介绍
- 博客分类:
- Java
最近在项目中用到了APACHE SOAP的客户端调用。记录一下
SOAP(简单对象访问协议)是一种利用XML编码数据的有线协议。它是同类协议中要求最低的一个规范,只定义了有线协议所要求的最关键的部分,有意地忽略了垃圾收集、对象激活等方面的细节。
SOAP对于Java开发者来说尤其重要,因为它让平台无关和可移植的Java程序更容易协同操作,使得Java的宝贵特性进一步增值。事实上,如果 Java 2平台企业版(J2EE)的下一个版本让SOAP成为一种必须遵循的有线协议,规定所有遵从J2EE规范的应用服务器都必须支持SOAP协议,我也不会感 到奇怪。不过就现在来说,我想我的猜想应该暂停了。
这个系列的文章总共四篇,这是第二篇。在这里,我要介绍的是Apache SOAP实现。
一、Apache SOAP概述
Apache SOAP,即Apache Software Foundation对SOAP规范的实现,建立在IBM的SOAP4J的基础上。和所有其他Apache工程类似,Apache SOAP源代码开放,按照Apache许可协议发行。我觉得这是当前最好的SOAP实现之一。然而,虽然Apache SOAP遵从SOAP规范1.1版本,但它缺乏SOAP 1.1所包含的某些功能(参见本文最后的“参考资源”,了解Apache SOAP支持功能的清单)。
1.1、下载和安装Apache SOAP
如前所述,Apache SOAP可以免费下载(参见“参考资源”中提供的下载链接)。我为我的Windows NT便携机下载了soap-bin-2.0.zip文件,该文件包含Apache SOAP 2.0,这是写作本文时的最新版本。安装Apache SOAP可谓轻而易举,共包含如下三个简单的步骤:
解开下载所得文件的ZIP压缩:解开压缩之后就得到了一个soap-2_0子目录。我把ZIP文件的内容解压缩到E盘的根目录下,因此有了一个包含Apache SOAP的E:\soap-2_0目录。
配置Web环境:要有一个支持Servlet和JSP的Web服务器。至此,你可能遇到下面两种情况之一:
情 况1:已经有一个支持Servlet和JSP的Web服务器,而且你觉得配置这个服务器没有问题。在这种情况下,请配置服务器,使得浏览器可以访问 http://localhost:8080/apache-soap/,打开soap-2_0 \webapps\soap\目录下面的index.html文件。
情况2:没有支持Servlet和JSP的Web服务器,或者虽然有这样一个服务器,却不想拿它做试验。在这种情 况下,我建议你下载Tomcat的最新版本(写作本文时,最新版本是3.1)(参见“参考资源”中的链接)。Tomcat是Apache创建和免费提供给 软件开发者的又一个优秀软件。下载合适的ZIP文件之后(jakarta-tomcat-3.1.1.zip),解开压缩时创建一个jakarta- tomcat子目录。和前面相似,我把解压缩得到的文件放入E盘的根目录之下。在jakarta-tomcat\conf\server.xml配置文件 中增加一个新的<Context>标记,如下所示:
<Context path="/apache-soap" docBase="E:/soap-2_0/webapps/soap"
debug="1" reloadable="true">
</Context> 在Context元素的docBase属性中,你应该在指定soap-2_0目录时把E:替换成合适的盘符。要启动Tomcat,执行 startup.bat(对于Unix,执行startup.sh)。要关闭Tomcat,执行shutdown.bat(对于Unix,执行 shutdown.sh)。但请稍等——现在请不要启动Tomcat。
设 置Web服务器classpath:Apache SOAP要求有1.1.2版本或更高的Apache Xerces(Java),它支持DOM(文档对象模型)Level 2规范,支持名称空间。我使用1.2版本的Xerces,即Apache网站的Xerces-J-bin.1.2.0.zip。解开这个压缩文件,得到 xerces-1_2_0子目录。和前面一样,我把解压缩得到的文件放到了E:盘的根目录之下。你应该配置Web服务器,使它能够用 xerces.jar(它在xerces-1_2_0子目录下)进行所有XML解析——而不是用服务器附带的库或jar解析XML。例如,Tomcat附 带了一个XML解析器(jakarta-tomcat\lib\xml.jar),支持DOM Level 1接口。即使你把xerces.jar放入了classpath,Tomcat下运行的Java代码也可能找错接口,因为在用来启动Tomcat的 Shell脚本/批命令文件中,xerces.jar被放到了classpath的最后。因此,必须编辑jakarta-tomcat\bin目录下的 tomcat.bat(对于Unix,则是tomcat.sh),把xerces.jar放到classpath的前面。下面是我在jakarta- tomcat\bin\tomcat.bat文件中所作的修改:
set CLASSPATH=E:\xerces-1_2_0\xerces.jar;%CLASSPATH%;%cp% 如果你在第二个步骤中属于情况2,也必须配置服务器,使它能够使用xerces.jar。
不管你属于哪一种情况,除了配置xerces.jar之外,你还必须配置Web服务器的classpath使它能够使用soap-2_0\lib\目录下的soap.jar。
1.2、检查安装是否成功
现在,启动Web服务器,用浏览器打开http://localhost:8080/apache-soap/admin,验证安装是否成功。这时,你应该看到下图所示的管理屏幕。
图一:Web界面的Apache SOAP管理工具
二、实例:HelloWorld
现在你已经设置好了Apache SOAP,我们来实际使用一下,构造一个简单的HelloWorld应用。在SOAP术语中,应用称为服务。一般地,创建服务分两个步骤,这两个步骤可能 由同一个人或组织实施,也可能不是。第一个步骤是在选定的语言中定义和实现服务本身,这里我们选择Java语言。第二个步骤是创建实际调用服务的客户程 序。首先我们来看HelloWorld服务。
2.1、HelloWorld服务
我在第一篇文章中讨论了一个用SOAP实现的HelloWorld服务实例。这个服务要求输入一个用户名字,返回一个定制的Hello消息给调用者。下面的代码显示了HelloWorld服务的完整Java代码。
package hello;
public class HelloServer
{
public String sayHelloTo(String name)
{
System.out.println("sayHelloTo(String name)");
return "Hello " + name + ", How are you doing?";
}
}
这就是全部的代码吗?如果这是真的话,实在太简单了!
Apache SOAP使创建服务变得极其简单。服务主要由业务逻辑构成,不管服务以哪种方式提供给外界使用,业务逻辑代码都是必须编写的。换句话说,服务不会和任何 SOAP相关的代码混合,因为Apache SOAP底层体系——它由rpcrouter Servlet和soap.jar构成——帮助我们完成了所有错综复杂的工作。我们来简要地探讨一下这些错综复杂的工作究竟包含些什么,例 如,Apache SOAP如何处理HTTP上的远程过程调用(RPC)请求?理解这一点将给创建客户程序带来方便(不错,是客户程序)。
在Apache SOAP中,org.apache.soap.rpc包支持在SOAP上进行RPC调用。Apache RPC支持的关键在于对象ID。所有的Apache SOAP服务必须有一个唯一的ID,它被视为服务的对象ID。众所周知,唯一性是一个相对的概念;在Apache SOAP中,对象ID的唯一性相对于服务所部署的Apache SOAP服务器而言。也就是说,部署在不同Apache SOAP服务器上的两个服务可能有同样的对象ID。
想要使用服务的客户程序设置一个org.apache.soap.rpc.Call对象,指定目标服务的对象ID、待调用方法的名字以及提供给方法的参数 (如果有的话)。设置好Call对象之后,客户程序调用它的invoke()方法。invoke()方法需要两个参数,第一个参数是一个执行 rpcrouter Servlet的URL,如http://localhost:8080/apache-soap/servlet/rpcrouter;第二个参数是 SOAPAction头(请参考本系列的第一篇文章,了解SOAPAction头的重要性和可能的取值)。
invoke()方法把Call对象转换成XML SOAP请求(类似第一篇文章所提供的示例),把请求发送给第一个参数中的URL所指定的rpcrouter Servlet。当Servlet返回应答,invoke()方法返回一个org.apache.soap.rpc.Response对象,这个对象包含 了服务的应答(如果有的话)或错误信息(如果出现了错误)。HTTP规定每一个请求都必须有一个应答;因此,即使服务本身不返回任何东 西,rpcrouter Servlet总是会返回一些内容。因此,invoke()方法总是返回一个Response对象。
在服务端,Apache SOAP服务器(也就是rpcrouter Servlet)接收客户程序发送的SOAP请求,重新构造出Call对象。Servlet使用重新构造得到的Call对象中的对象ID在服务管理器中确定具体的对象。
接下来,Servlet在已经确定的对象上检验被调用方法的名字并调用方法。完成后,Servlet串行化该调用的返回值,在HTTP应答中把它发送给客户程序。
从上述讨论中,我们可以发现一个有趣的问题:Apache SOAP如何知道串行化某种给定数据类型的方法?Apache SOAP通过一个类型注册器(org.apache.soap.encoding.SOAPMappingRegistry),以及通过所有装配器 (marshaller)和反装配器(marshaller)分别必须实现的串行化 (org.apache.soap.util.xml.Serializer)和反串行化 (org.apache.soap.util.xml.Deserialization)接口,实现Java数据类型和XML之间的装配和反装配。 Apache SOAP提供了许多实现这些接口的内建的装配器和反装配器。例如,我们可以用 org.apache.soap.encoding.soapenc.BeanSerializer类装配和反装配JavaBean。本文后面我将介绍如 何使用这个类。
对于Java基本数据类型(int,long,double,等等)及其对应的对象化形式(Integer,Long,Double,等等)来说,它们的 串行化器和反串行化器已经预先在类型映射注册器中注册。因此,对于客户程序来说,用Java基本数据类型及其对象形式作为方法参数是无缝的。然而,如果服 务所要求的参数是一个JavaBean,则必须手工在类型映射注册器中注册BeanSerializer。服务永远不会做任何额外的工作,最后客户程序的 负担总是较多。在这个系列的第四篇文章中,我将介绍用动态代理构造的框架,它将使创建客户程序和创建服务程序一样简单。
2.2、部署HelloWorld服务
部署Apache SOAP服务有两种方法:使用Web界面的管理工具,或通过命令行进行部署。所有这两种方法都可以部署服务,使服务可被客户程序访问。
■ 使用管理工具部署服务
要使用管理工具,用浏览器打开http://localhost:8080/apache-soap/admin。浏览器将显示出图一所示的界面。点击窗 口左边的Deploy按钮,一个带有许多输入框的窗口就会出现。并非所有的输入框现在都要用到。我将在用到这些输入框的时候介绍它们的含义。由于本文无需 用到所有的输入框,所以我们将忽略部分输入框的含义。但不用担心,到第三篇文章结束时,我将介绍完所有的输入框。
ID输入框用来设置对象ID;如前所述,SOAP基础设施利用对象ID把RPC请求绑定到SOAP服务。我在前面已经提到,所有Apache SOAP服务必须有一个对象ID,这个对象ID在该服务器上部署的所有服务之间唯一。我通常使用 “urn:<UniqueServiceID>”格式,其中UniqueServiceID是服务的唯一对象ID。在本例 中,把ID设置成“urn:hello”。
Scope输入框用来定义响应调用请求的服务实例的生存范围和时间。Scope可以是下列值之一:
page:服务实例一直有效,直至应答发送完毕或把请求传递给了另一个页面——如果使用标准的部署机制,向前传递请求不太可能发生。
request:服务实例在请求处理期间一直有效,不管是否出现请求传递。
session:服务实例对于整个会话都有效。
application:服务实例被用于所有对服务的调用请求。 Scope的值对安全有着重要的影响,记住这一点很重要。page和request值确保了连续调用之间的隔离。在另一个极端,application值 意味着所有SOAP的用户共享服务实例。细心的读者可能已经注意到,JSP的<jsp:useBean>标记同样要用到这些值。事实 上,rpcrouter Servlet曾经就是一个JSP页面,这也许是这些值被选用的原因。在本例中,我们把Scope的值设置成application。
在Methods输入框中,输入用空白字符分隔的方法名字,这些方法名字指示出当前部署的服务上允许调用的方法。我们的服务示例只支持一个方法,即sayHelloTo()。
把Provider Type设置成Java。它意味着服务用Java实现,而且你必须为Apache SOAP提供服务完整的类名。这个任务在Provider Class输入框完成,我们把它设置成hello.HelloServer。由于sayHelloTo()方法不是静态的,保持Static输入框原来的 值,即no。
现在滚动到页面的下方,点击表单下面的Deploy按钮(不是左边的Deploy按钮)。要验证服务已经部署完毕,点击左边的List按钮,这时列表所显示的服务中应该包含一个urn:Hello服务。
■ 从命令行部署服务
部署服务除了可以用Web界面的管理工具,还可以用命令行Java工具 org.apache.soap.server.ServiceManagerClient,它是一个Apache SOAP附带的类。这个类要求有两个必不可少的参数,一个指向Apache SOAP路由Servlet(即rpcrouter)的URL,以及一个动作。这个动作可以是以下四者之一:deploy,undeploy,list, 或query。根据指定动作的不同,有时候还要提供额外的参数。例如,如果动作是deploy,则必须提供XML部署描述器文件的名字。部署描述器文件应 该包含Apache SOAP服务器成功部署服务所需要的全部信息。例如,描述HelloWorld部署细节的部署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:service>
上述XML代码所包含的信息和我们在Web界面的管理工具中所输入的信息一样。接下来,输入下面的命令,从命令行部署HelloWorld服务:
java org.apache.soap.server.ServiceManagerClient
http://localhost:8080/apache-soap/servlet/rpcrouter
deploy DeploymentDescriptor.xml
DeploymentDescriptor.xml是上面显示的描述部署信息的XML文件名字。要验证服务是否部署成功,输入以下命令:
java org.apache.soap.server.ServiceManagerClient
http://localhost:8080/apache-soap/servlet/rpcrouter query urn:Hello
这时,我们应该看到和DeploymentDescriptor.xml文件内一样的XML。
2.3、HelloWorld客户程序
编写客户程序要比编写HelloWorld服务复杂得多。不过,你应该不会对此感到奇怪,因为前面已经提到,客户程序(至少)必须负责设置Call对象, 这需要不少工作。顺便说一下,本系列文章的第四篇将介绍一个框架,这个框架以Java 2 1.3版新引入的动态代理类为基础,使创建客户程序和创建服务一样简单。
Listing 1显示了完整的客户程序。接下来我们一步一步地仔细看看这个程序。这个程序需要一个必不可少的参数:程序要向他说Hello信息的用户名字。
Listing 1: Client.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;
public class Client
{
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 call = new Call();
call.setTargetObjectURI("urn:Hello");
call.setMethodName("sayHelloTo");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
Vector params = new Vector();
params.addElement(new Parameter("name", String.class, name, 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();
}
}
}
客户程序首先设置Call对象,它需要如下信息:
被调用服务的对象ID,它通过Call对象的setTargetObjectURI()方法设置。本例的对象ID是urn:Hello。
待调用方法的名字,它通过Call对象的setMethodName()方法设置。本例的方法名字是sayHelloTo()。
参数的编码方式,它通过Call对象的setEncodingStyleURI()方法设置。本例我们使用标准的SOAP编码方式,这种编码方式由名称空间http://schemas.xmlsoap.org/soap/encoding/定义。
方 法调用的参数通过Call对象的setParams()方法设置。setParams()方法的参数是一个Java Vector(向量)。这个向量包含所有的参数,向量中索引为0的参数是被调用方法从左边数起的第一个参数,索引为1的参数是被调用方法从左边数起的第二 个参数,依此类推。向量中的每一个元素都是一个org.apache.soap.rpc.Parameter的实例。Parameter构造函数要求指定 参数的名字、Java类型和值,还有一个可选的编码方式。如果指定了null编码方式(正如本例所做的那样),则默认使用Call对象的编码方式。虽然每 一个参数对应着一个名字,但这个名字可以设置成任何内容,Apache SOAP服务器调用方法时不会用到这个名字。因此,绝对有必要让向量中参数的次序和被调用方法的参数次序一致。 下面的代码片断显示了客户程序创建Call对象的过程:
// 构造Call对象
Call call = new Call();
call.setTargetObjectURI("urn:Hello");
call.setMethodName("sayHelloTo");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
Vector params = new Vector();
params.addElement(new Parameter("name", String.class, name, null));
call.setParams(params);
现在,该是实际调用HelloWorld远程服务所提供方法的时候了。为此,客户程序调用了Call对象的invoke()方法,这个方法返回一个org.apache.soap.rpc.Response对象,如下所示:
// 发出调用
Response resp = null;
try
{
resp = call.invoke(url, "");
}
catch( SOAPException e )
{
System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " +
e.getMessage());
System.exit(-1);
}
接下来,客户程序检查Response对象。如果方法调用过程中出现了错误,generateFault()方法返回一个true值,客户程序提取并显示实际的错误信息:
Fault fault = resp.getFault();
System.err.println("Generated fault: ");
System.out.println (" Fault Code = " + fault.getFaultCode());
System.out.println (" Fault String = " + fault.getFaultString());
如果方法调用成功,则客户程序提取并显示Hello信息:
// 检查应答
if( !resp.generatedFault() )
{
Parameter ret = resp.getReturnValue();
Object value = ret.getValue();
System.out.println(value);
}
三、带有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
{
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。
SOAP对于Java开发者来说尤其重要,因为它让平台无关和可移植的Java程序更容易协同操作,使得Java的宝贵特性进一步增值。事实上,如果 Java 2平台企业版(J2EE)的下一个版本让SOAP成为一种必须遵循的有线协议,规定所有遵从J2EE规范的应用服务器都必须支持SOAP协议,我也不会感 到奇怪。不过就现在来说,我想我的猜想应该暂停了。
这个系列的文章总共四篇,这是第二篇。在这里,我要介绍的是Apache SOAP实现。
一、Apache SOAP概述
Apache SOAP,即Apache Software Foundation对SOAP规范的实现,建立在IBM的SOAP4J的基础上。和所有其他Apache工程类似,Apache SOAP源代码开放,按照Apache许可协议发行。我觉得这是当前最好的SOAP实现之一。然而,虽然Apache SOAP遵从SOAP规范1.1版本,但它缺乏SOAP 1.1所包含的某些功能(参见本文最后的“参考资源”,了解Apache SOAP支持功能的清单)。
1.1、下载和安装Apache SOAP
如前所述,Apache SOAP可以免费下载(参见“参考资源”中提供的下载链接)。我为我的Windows NT便携机下载了soap-bin-2.0.zip文件,该文件包含Apache SOAP 2.0,这是写作本文时的最新版本。安装Apache SOAP可谓轻而易举,共包含如下三个简单的步骤:
解开下载所得文件的ZIP压缩:解开压缩之后就得到了一个soap-2_0子目录。我把ZIP文件的内容解压缩到E盘的根目录下,因此有了一个包含Apache SOAP的E:\soap-2_0目录。
配置Web环境:要有一个支持Servlet和JSP的Web服务器。至此,你可能遇到下面两种情况之一:
情 况1:已经有一个支持Servlet和JSP的Web服务器,而且你觉得配置这个服务器没有问题。在这种情况下,请配置服务器,使得浏览器可以访问 http://localhost:8080/apache-soap/,打开soap-2_0 \webapps\soap\目录下面的index.html文件。
情况2:没有支持Servlet和JSP的Web服务器,或者虽然有这样一个服务器,却不想拿它做试验。在这种情 况下,我建议你下载Tomcat的最新版本(写作本文时,最新版本是3.1)(参见“参考资源”中的链接)。Tomcat是Apache创建和免费提供给 软件开发者的又一个优秀软件。下载合适的ZIP文件之后(jakarta-tomcat-3.1.1.zip),解开压缩时创建一个jakarta- tomcat子目录。和前面相似,我把解压缩得到的文件放入E盘的根目录之下。在jakarta-tomcat\conf\server.xml配置文件 中增加一个新的<Context>标记,如下所示:
<Context path="/apache-soap" docBase="E:/soap-2_0/webapps/soap"
debug="1" reloadable="true">
</Context> 在Context元素的docBase属性中,你应该在指定soap-2_0目录时把E:替换成合适的盘符。要启动Tomcat,执行 startup.bat(对于Unix,执行startup.sh)。要关闭Tomcat,执行shutdown.bat(对于Unix,执行 shutdown.sh)。但请稍等——现在请不要启动Tomcat。
设 置Web服务器classpath:Apache SOAP要求有1.1.2版本或更高的Apache Xerces(Java),它支持DOM(文档对象模型)Level 2规范,支持名称空间。我使用1.2版本的Xerces,即Apache网站的Xerces-J-bin.1.2.0.zip。解开这个压缩文件,得到 xerces-1_2_0子目录。和前面一样,我把解压缩得到的文件放到了E:盘的根目录之下。你应该配置Web服务器,使它能够用 xerces.jar(它在xerces-1_2_0子目录下)进行所有XML解析——而不是用服务器附带的库或jar解析XML。例如,Tomcat附 带了一个XML解析器(jakarta-tomcat\lib\xml.jar),支持DOM Level 1接口。即使你把xerces.jar放入了classpath,Tomcat下运行的Java代码也可能找错接口,因为在用来启动Tomcat的 Shell脚本/批命令文件中,xerces.jar被放到了classpath的最后。因此,必须编辑jakarta-tomcat\bin目录下的 tomcat.bat(对于Unix,则是tomcat.sh),把xerces.jar放到classpath的前面。下面是我在jakarta- tomcat\bin\tomcat.bat文件中所作的修改:
set CLASSPATH=E:\xerces-1_2_0\xerces.jar;%CLASSPATH%;%cp% 如果你在第二个步骤中属于情况2,也必须配置服务器,使它能够使用xerces.jar。
不管你属于哪一种情况,除了配置xerces.jar之外,你还必须配置Web服务器的classpath使它能够使用soap-2_0\lib\目录下的soap.jar。
1.2、检查安装是否成功
现在,启动Web服务器,用浏览器打开http://localhost:8080/apache-soap/admin,验证安装是否成功。这时,你应该看到下图所示的管理屏幕。
图一:Web界面的Apache SOAP管理工具
二、实例:HelloWorld
现在你已经设置好了Apache SOAP,我们来实际使用一下,构造一个简单的HelloWorld应用。在SOAP术语中,应用称为服务。一般地,创建服务分两个步骤,这两个步骤可能 由同一个人或组织实施,也可能不是。第一个步骤是在选定的语言中定义和实现服务本身,这里我们选择Java语言。第二个步骤是创建实际调用服务的客户程 序。首先我们来看HelloWorld服务。
2.1、HelloWorld服务
我在第一篇文章中讨论了一个用SOAP实现的HelloWorld服务实例。这个服务要求输入一个用户名字,返回一个定制的Hello消息给调用者。下面的代码显示了HelloWorld服务的完整Java代码。
package hello;
public class HelloServer
{
public String sayHelloTo(String name)
{
System.out.println("sayHelloTo(String name)");
return "Hello " + name + ", How are you doing?";
}
}
这就是全部的代码吗?如果这是真的话,实在太简单了!
Apache SOAP使创建服务变得极其简单。服务主要由业务逻辑构成,不管服务以哪种方式提供给外界使用,业务逻辑代码都是必须编写的。换句话说,服务不会和任何 SOAP相关的代码混合,因为Apache SOAP底层体系——它由rpcrouter Servlet和soap.jar构成——帮助我们完成了所有错综复杂的工作。我们来简要地探讨一下这些错综复杂的工作究竟包含些什么,例 如,Apache SOAP如何处理HTTP上的远程过程调用(RPC)请求?理解这一点将给创建客户程序带来方便(不错,是客户程序)。
在Apache SOAP中,org.apache.soap.rpc包支持在SOAP上进行RPC调用。Apache RPC支持的关键在于对象ID。所有的Apache SOAP服务必须有一个唯一的ID,它被视为服务的对象ID。众所周知,唯一性是一个相对的概念;在Apache SOAP中,对象ID的唯一性相对于服务所部署的Apache SOAP服务器而言。也就是说,部署在不同Apache SOAP服务器上的两个服务可能有同样的对象ID。
想要使用服务的客户程序设置一个org.apache.soap.rpc.Call对象,指定目标服务的对象ID、待调用方法的名字以及提供给方法的参数 (如果有的话)。设置好Call对象之后,客户程序调用它的invoke()方法。invoke()方法需要两个参数,第一个参数是一个执行 rpcrouter Servlet的URL,如http://localhost:8080/apache-soap/servlet/rpcrouter;第二个参数是 SOAPAction头(请参考本系列的第一篇文章,了解SOAPAction头的重要性和可能的取值)。
invoke()方法把Call对象转换成XML SOAP请求(类似第一篇文章所提供的示例),把请求发送给第一个参数中的URL所指定的rpcrouter Servlet。当Servlet返回应答,invoke()方法返回一个org.apache.soap.rpc.Response对象,这个对象包含 了服务的应答(如果有的话)或错误信息(如果出现了错误)。HTTP规定每一个请求都必须有一个应答;因此,即使服务本身不返回任何东 西,rpcrouter Servlet总是会返回一些内容。因此,invoke()方法总是返回一个Response对象。
在服务端,Apache SOAP服务器(也就是rpcrouter Servlet)接收客户程序发送的SOAP请求,重新构造出Call对象。Servlet使用重新构造得到的Call对象中的对象ID在服务管理器中确定具体的对象。
接下来,Servlet在已经确定的对象上检验被调用方法的名字并调用方法。完成后,Servlet串行化该调用的返回值,在HTTP应答中把它发送给客户程序。
从上述讨论中,我们可以发现一个有趣的问题:Apache SOAP如何知道串行化某种给定数据类型的方法?Apache SOAP通过一个类型注册器(org.apache.soap.encoding.SOAPMappingRegistry),以及通过所有装配器 (marshaller)和反装配器(marshaller)分别必须实现的串行化 (org.apache.soap.util.xml.Serializer)和反串行化 (org.apache.soap.util.xml.Deserialization)接口,实现Java数据类型和XML之间的装配和反装配。 Apache SOAP提供了许多实现这些接口的内建的装配器和反装配器。例如,我们可以用 org.apache.soap.encoding.soapenc.BeanSerializer类装配和反装配JavaBean。本文后面我将介绍如 何使用这个类。
对于Java基本数据类型(int,long,double,等等)及其对应的对象化形式(Integer,Long,Double,等等)来说,它们的 串行化器和反串行化器已经预先在类型映射注册器中注册。因此,对于客户程序来说,用Java基本数据类型及其对象形式作为方法参数是无缝的。然而,如果服 务所要求的参数是一个JavaBean,则必须手工在类型映射注册器中注册BeanSerializer。服务永远不会做任何额外的工作,最后客户程序的 负担总是较多。在这个系列的第四篇文章中,我将介绍用动态代理构造的框架,它将使创建客户程序和创建服务程序一样简单。
2.2、部署HelloWorld服务
部署Apache SOAP服务有两种方法:使用Web界面的管理工具,或通过命令行进行部署。所有这两种方法都可以部署服务,使服务可被客户程序访问。
■ 使用管理工具部署服务
要使用管理工具,用浏览器打开http://localhost:8080/apache-soap/admin。浏览器将显示出图一所示的界面。点击窗 口左边的Deploy按钮,一个带有许多输入框的窗口就会出现。并非所有的输入框现在都要用到。我将在用到这些输入框的时候介绍它们的含义。由于本文无需 用到所有的输入框,所以我们将忽略部分输入框的含义。但不用担心,到第三篇文章结束时,我将介绍完所有的输入框。
ID输入框用来设置对象ID;如前所述,SOAP基础设施利用对象ID把RPC请求绑定到SOAP服务。我在前面已经提到,所有Apache SOAP服务必须有一个对象ID,这个对象ID在该服务器上部署的所有服务之间唯一。我通常使用 “urn:<UniqueServiceID>”格式,其中UniqueServiceID是服务的唯一对象ID。在本例 中,把ID设置成“urn:hello”。
Scope输入框用来定义响应调用请求的服务实例的生存范围和时间。Scope可以是下列值之一:
page:服务实例一直有效,直至应答发送完毕或把请求传递给了另一个页面——如果使用标准的部署机制,向前传递请求不太可能发生。
request:服务实例在请求处理期间一直有效,不管是否出现请求传递。
session:服务实例对于整个会话都有效。
application:服务实例被用于所有对服务的调用请求。 Scope的值对安全有着重要的影响,记住这一点很重要。page和request值确保了连续调用之间的隔离。在另一个极端,application值 意味着所有SOAP的用户共享服务实例。细心的读者可能已经注意到,JSP的<jsp:useBean>标记同样要用到这些值。事实 上,rpcrouter Servlet曾经就是一个JSP页面,这也许是这些值被选用的原因。在本例中,我们把Scope的值设置成application。
在Methods输入框中,输入用空白字符分隔的方法名字,这些方法名字指示出当前部署的服务上允许调用的方法。我们的服务示例只支持一个方法,即sayHelloTo()。
把Provider Type设置成Java。它意味着服务用Java实现,而且你必须为Apache SOAP提供服务完整的类名。这个任务在Provider Class输入框完成,我们把它设置成hello.HelloServer。由于sayHelloTo()方法不是静态的,保持Static输入框原来的 值,即no。
现在滚动到页面的下方,点击表单下面的Deploy按钮(不是左边的Deploy按钮)。要验证服务已经部署完毕,点击左边的List按钮,这时列表所显示的服务中应该包含一个urn:Hello服务。
■ 从命令行部署服务
部署服务除了可以用Web界面的管理工具,还可以用命令行Java工具 org.apache.soap.server.ServiceManagerClient,它是一个Apache SOAP附带的类。这个类要求有两个必不可少的参数,一个指向Apache SOAP路由Servlet(即rpcrouter)的URL,以及一个动作。这个动作可以是以下四者之一:deploy,undeploy,list, 或query。根据指定动作的不同,有时候还要提供额外的参数。例如,如果动作是deploy,则必须提供XML部署描述器文件的名字。部署描述器文件应 该包含Apache SOAP服务器成功部署服务所需要的全部信息。例如,描述HelloWorld部署细节的部署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:service>
上述XML代码所包含的信息和我们在Web界面的管理工具中所输入的信息一样。接下来,输入下面的命令,从命令行部署HelloWorld服务:
java org.apache.soap.server.ServiceManagerClient
http://localhost:8080/apache-soap/servlet/rpcrouter
deploy DeploymentDescriptor.xml
DeploymentDescriptor.xml是上面显示的描述部署信息的XML文件名字。要验证服务是否部署成功,输入以下命令:
java org.apache.soap.server.ServiceManagerClient
http://localhost:8080/apache-soap/servlet/rpcrouter query urn:Hello
这时,我们应该看到和DeploymentDescriptor.xml文件内一样的XML。
2.3、HelloWorld客户程序
编写客户程序要比编写HelloWorld服务复杂得多。不过,你应该不会对此感到奇怪,因为前面已经提到,客户程序(至少)必须负责设置Call对象, 这需要不少工作。顺便说一下,本系列文章的第四篇将介绍一个框架,这个框架以Java 2 1.3版新引入的动态代理类为基础,使创建客户程序和创建服务一样简单。
Listing 1显示了完整的客户程序。接下来我们一步一步地仔细看看这个程序。这个程序需要一个必不可少的参数:程序要向他说Hello信息的用户名字。
Listing 1: Client.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;
public class Client
{
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 call = new Call();
call.setTargetObjectURI("urn:Hello");
call.setMethodName("sayHelloTo");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
Vector params = new Vector();
params.addElement(new Parameter("name", String.class, name, 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();
}
}
}
客户程序首先设置Call对象,它需要如下信息:
被调用服务的对象ID,它通过Call对象的setTargetObjectURI()方法设置。本例的对象ID是urn:Hello。
待调用方法的名字,它通过Call对象的setMethodName()方法设置。本例的方法名字是sayHelloTo()。
参数的编码方式,它通过Call对象的setEncodingStyleURI()方法设置。本例我们使用标准的SOAP编码方式,这种编码方式由名称空间http://schemas.xmlsoap.org/soap/encoding/定义。
方 法调用的参数通过Call对象的setParams()方法设置。setParams()方法的参数是一个Java Vector(向量)。这个向量包含所有的参数,向量中索引为0的参数是被调用方法从左边数起的第一个参数,索引为1的参数是被调用方法从左边数起的第二 个参数,依此类推。向量中的每一个元素都是一个org.apache.soap.rpc.Parameter的实例。Parameter构造函数要求指定 参数的名字、Java类型和值,还有一个可选的编码方式。如果指定了null编码方式(正如本例所做的那样),则默认使用Call对象的编码方式。虽然每 一个参数对应着一个名字,但这个名字可以设置成任何内容,Apache SOAP服务器调用方法时不会用到这个名字。因此,绝对有必要让向量中参数的次序和被调用方法的参数次序一致。 下面的代码片断显示了客户程序创建Call对象的过程:
// 构造Call对象
Call call = new Call();
call.setTargetObjectURI("urn:Hello");
call.setMethodName("sayHelloTo");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
Vector params = new Vector();
params.addElement(new Parameter("name", String.class, name, null));
call.setParams(params);
现在,该是实际调用HelloWorld远程服务所提供方法的时候了。为此,客户程序调用了Call对象的invoke()方法,这个方法返回一个org.apache.soap.rpc.Response对象,如下所示:
// 发出调用
Response resp = null;
try
{
resp = call.invoke(url, "");
}
catch( SOAPException e )
{
System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " +
e.getMessage());
System.exit(-1);
}
接下来,客户程序检查Response对象。如果方法调用过程中出现了错误,generateFault()方法返回一个true值,客户程序提取并显示实际的错误信息:
Fault fault = resp.getFault();
System.err.println("Generated fault: ");
System.out.println (" Fault Code = " + fault.getFaultCode());
System.out.println (" Fault String = " + fault.getFaultString());
如果方法调用成功,则客户程序提取并显示Hello信息:
// 检查应答
if( !resp.generatedFault() )
{
Parameter ret = resp.getReturnValue();
Object value = ret.getValue();
System.out.println(value);
}
三、带有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
{
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。
发表评论
-
[转] Java注释@interface的用法
2011-05-16 17:35 25976@interface是用来自定义JAVA Annot ... -
Object转BigDecimal
2011-01-03 12:43 20014项目中遇到类似问题,记下来先: import j ... -
http响应代码
2010-12-19 21:54 1022来源于网络 写 ... -
[转]通过jdbc-odbc桥来访问excel文件
2010-12-19 21:51 1376记录,备用 通过jdbc-odbc桥来访问excel文 ... -
application/xxxx等字符编码的解释说明、请缓存操作以及oracle decode函数示例
2010-11-21 21:33 10001. 关于application/x-www-f ... -
非安装版Tomcat指向JDK目录
2010-09-25 12:16 14791,修改bin文件夹下面的catalina.bat文件, ... -
[转]解决Tomcat Error listenerStart 问题
2010-07-11 21:53 2121原文地址:http://hi.baidu.com/zh ... -
Eclipse常用快捷键(英文整理版)及常用设置
2010-06-09 00:19 4990CTRL + DDelete row. Try it! ... -
tomcat报错 java.io.EOFException
2010-04-05 23:26 1152今天在修改了一个接口实现方法后保存后,重启tomcat报错: ... -
[转]JTable的自动换行处理
2010-03-31 23:17 4257下面是一个简单示例: 1.创建一个简单的JTabl ...
相关推荐
在这个"apache soap的hello例子"中,我们将探讨如何使用Apache SOAP实现一个简单的“Hello World”服务。 首先,让我们理解SOAP的基本结构。SOAP消息通常包含三个主要部分:Header、Body和Envelope。Header用于传递...
Apache SOAP是Java平台上的一个开源库,用于构建和消费基于SOAP(Simple Object Access Protocol)的Web服务。SOAP是一种XML格式的协议,它允许应用程序通过HTTP或其他传输协议进行远程调用,实现了分布式计算的能力...
### Apache与SOAP技术详解 #### 引言 在IT领域,Apache与SOAP是两个关键的技术概念,它们在分布式系统和网络通信中扮演着至关重要的角色。Apache作为一个强大的Web服务器软件,而SOAP则是一种用于交换结构化信息的...
该 jar 包在 Apache 已经不能下载了。希望能对大家有帮助。
最后,"将解析后的SOAP转成Java实体"意味着将解析出的数据映射到预先定义好的Java类实例上。例如,如果我们有类`YourResponseType`代表返回的数据结构,我们可以使用工具如Apache BeanUtils将XML节点值填充到这些...
SOAP(Simple Object Access Protocol)是一种基于XML的协议,用于在Web服务中交换结构化和类型化的信息。在Java环境中,为了实现SOAP通信,通常需要一些特定的库支持,这些库通常以JAR(Java Archive)文件的形式...
Apache Axis是一个流行的开源工具,它使得开发者能够方便地创建和部署SOAP Web服务。通过Apache Axis,我们可以将Java方法暴露为Web服务,或者调用其他系统提供的SOAP服务。以下是使用Apache Axis进行Web Service...
此外,考虑使用Apache CXF、JAX-WS RI等成熟框架,它们提供了更高级别的抽象和便利的API,简化了SOAP客户端的开发。 通过以上知识点的学习和实践,你可以掌握如何使用Java调用SOAP接口,以及如何根据具体需求进行...
根据提供的文档内容,本文主要介绍了如何在WebService中利用Java SOAP技术处理复杂的Java数据类型,特别是数组及JavaBean等。文章通过具体的示例讲解了如何创建一个可以处理股票交易量的服务,并详细介绍了服务端的...
3. **Apache SOAP安装**:Apache SOAP库是Java平台上的一个实现,提供了SOAP客户端和服务器端的实现。下载Apache SOAP库,将其添加到你的项目类路径中,以便在Java程序中使用SOAP功能。 4. **编写Server和Client端...
适用于soap消息的发送与接收org.apache.soap.Envelope envelope = new org.apache.soap.Envelope();
本文将详细介绍如何利用CXF来打印SOAP报文以及记录WebService的日志。 #### 一、背景介绍 在实际开发过程中,对于WebService的请求和响应信息进行日志记录是非常必要的。这有助于我们在出现问题时能够快速定位问题...
- 第三方库:有很多专门为处理SOAP消息的库,如Python的`suds-jurko`,Java的`Apache CXF`,它们提供了方便的方法直接获取Body内容。 4. 示例代码: 在Python中,可以使用`xml.etree.ElementTree`来提取Body: ...
本篇文章将深入探讨这两个主题,并介绍如何使用SOAP抓取工具来分析和理解WebService的通信过程。 首先,WebService是一种基于开放标准(如XML、WSDL和SOAP)的软件接口,它允许不同系统之间通过互联网交换数据和...
在安装了SOAP库之前,我们需要下载Apache SOAP 2.3.1版本。这是一个提供SOAP支持的库,使我们能够创建和消费SOAP消息。解压缩下载的文件,找到`webapps`目录下的`soap.war`文件。将此文件复制到Tomcat的`webapps`...
Apache SOAP提供了Java API,使得开发者可以轻松地创建SOAP服务器和客户端程序。通过这个库,你可以创建基于SOAP的服务,例如演示中的"HelloWorld"示例,以及更复杂的如"bidbuy"、"com"和"mine"的示例应用。 为了...
Apache Axis 是一个开源的Web服务框架,主要用于构建和部署基于SOAP(Simple Object Access Protocol)的Web服务。这个框架是Apache软件基金会的一个项目,为开发者提供了一种高效、灵活且易于使用的工具,使得在...
要实现节点首字母小写转大写的转换,我们需要遍历整个SOAP XML报文的OMElement树,并对每个节点进行处理。 以下是一个可能的Java代码实现步骤: 1. 创建一个递归方法,接收OMElement作为参数。 2. 在方法内部,...
AXIS是Apache软件基金会的一个开源项目,主要用于构建和部署SOAP Web服务。它提供了简单的命令行工具,可以将Java类自动转换为SOAP服务。例如,使用`wsdl2java`命令可以将WSDL文件转换为Java客户端和服务器端代码,...