番茄的梦想

那一抹夕阳

  博客园  :: 首页  ::  :: 联系 :: 订阅 订阅  :: 管理
这篇文章描述的是 Web 服务描述语言 (WSDL),WSDL是采用 XML语言来描述 Web 服务的
属性,例如它做什么,它位于哪里和怎样调用它。本文还介绍了 IBM 的 WSDL 工具包,
此工具包能够从 WSDL 中生成存根,并简化 Web 服务应用的创建过程。

欢迎进入本专栏的第 4 部分,本专栏重点讲述 Web 服务技术正在革新和创新的方面。
在第 3 部分(请参阅参考资料)中,我展示了简单对象存取协议 (SOAP) 如何在后台工
作。在这一部分中,我会解释 WSDL,这个描述 Web 服务的核心属性的标准方法,和一
些能支持 WSDL 来加速开发过程的工具。


工具和安装

我们将在这个部分使用两个新的工具:

IBM WSTK 2.1:IBM Web 服务工具包 2.1 (请参阅参考资料)包含 Apache SOAP、WSDL
生成器和通用描述、发现和集成 (UDDI) 客户端。由于在这一系列中我们一直都在使
用 Apache SOAP 2.0,所以我们继续使用它作为我们的 SOAP 服务器,但是这一部分我
们将使用 WSTK 的 WSDL 生成器。

IBM WSDL 1.1 工具包:IBM Web 服务描述语言工具包(请参阅参考资料)生成来自于
WSDL 的客户端和服务器的存根。它的代码被封装为 wsdl.jar,它使用 WSTK 的 bsf.j
ar (Bean 脚本框架)和 xalan.jar (XML 样式表单处理器)文件。

一旦您下载并安装了这些工具,请确保 wsdl.jar、bsf.jar 和 xalan.jar 在您的类路
径 (CLASSPATH) 下,这样您就可以准备构造你的第一个带有 WSDL 的程序了。


介绍 WSDL

Web 服务的一个主要思想,就是未来的应用将由一组应用了网络的服务组合而成。只要
两个等同的服务使用统一标准和中性的方法在网络上宣传自己,那么从理论上说,一个
应用程序就可以根据价格或者性能的标准,从两个彼此竞争的服务之中选出一个。除此
之外,一些服务允许在机器之间复制,因而可以通过把有用的服务复制到本地储存库,
来提高允许运行在特定的计算机(群)上的应用程序的性能。

如果您想一想,会发现这很类似于人力劳务市场的运作。提供工作的网站和雇佣公司为
工人和老板提供中介服务,利用简历和工作描述来加快匹配过程。如果找到了一个好的
匹配,感兴趣的双方就会尝试磋商可接受的条件。如果达成了协议,工人或者去老板那
里开工,或者利用因特网和远程通信来作为代替的工作途径。

Web 服务描述语言是 XML 中相当于简历的等同物 -- 描述 Web 服务做什么,它在哪里
及如何调用它。想知道它是什么样的,先看看 Xmethods 网站上运行的货币交换服务的
WSDL(请参阅参考资料)。如果访问过 http://www.xmethods.net/sd_ibm/CurrencyE
xchangeService.wsdl,您会看到服务的顶级描述。单击这个 WSDL 的 URL 地址,您就
会看到在清单 1 中的 WSDL 代码。

请注意,如果访问 XMethods 网站,对于每个 WSDL,您会看到两个版本,其中一个是特
别用于 IBM WSDL。这是因为当前 IBM 的 WSDL 工具包中有一个错误,就是不让它处理
来自于其他工具包的 WSDL。这个问题不久就会被修复;同时,我使用 XMethods 为创建
了一个特为本文服务的版本。

让我们来查看一下 WSDL 文档中的每一部分,从 <definitions> 段开始。

<definitions>
<definitions> 元素包括一个或者多个服务的定义。大多数情况下,一个 WSDL 文件定
义一个单独的服务。<definitions> 标记后通常紧跟着以下属性的声明:

name:这个属性是可选的,用来说明服务的主要目的。
targetNamespace:这个属性定义了关于服务信息的逻辑命名空间,并且各服务的属性值
通常是不同的。这个属性在稍后会作更进一步的讨论。
xmlns:tns:在许多的 WSDL 文件中,这个命名空间并不出现(包括我们的示例),但是
很快就会流行起来的。如果出现,则被设置成 targetNamespace 的值。这个属性在稍后
会作更进一步的讨论。
xmlns:soap 和 xmlns:xsd:它们是标准命名空间的定义,在以后的 WSDL 文档中被用作
指定特定的 SOAP 的信息和数据类型。
xmlns:缺省的 WSDL 文档的命名空间,被设置到 http://schemas.xmlsoap.org/wsdl/
。所有的 WSDL 标记,像 <definitions>、<message> 和 <service> 都驻留在这个命
名空间之内。

在 <definitions> 之中,有三个概念性的部分:

<message> 和 <portType>: 服务提供什么操作。
<binding>: 操作怎样被调用。
<service>: 服务位于哪里。

除此之外,服务所使用的任何复杂数据类型必须在一个可选的 <types> 部分里面被定义
,而 <types> 部分必须直接放在 <message> 部分之前。因为我们的示例是简单的而且
只使用原始的参数类型,因而没有 <types> 部分。

让我们详细的看看每一个部分。

<message> 和 <portType>
一个 <message> 对应在调用者和服务之间传递的一条信息。一个规则的有往返的远程方
法调用有两条消息,一条负责请求,一条负责响应。每一个 <message> 可以没有任何部
分,或者有多个部分,每个部分都有一个名字和可选的类型。当 WSDL 描述一个对象时
,每一个部分映射到一个方法调用的参数上。如果一个方法返回为 void,那么响应就是
一条空信息。
一个 <portType> 对应一套单个或多个操作,而一个 <operation> 定义了一个特定的输
入/输出消息序列。每一个输入/输出的消息属性必须对应前面定义过的 <message> 的
名称。如果一个操作只指定了输入,则只是单向操作。输出后面紧跟着输入则是请求-响
应 (solicit-response) 操作,单一的输入是一个通告。当 WSDL 描述一个对象时,每
一个 <operation> 映射一个方法并且每一个 <portType> 映射一个 Java 接口或类。
在这个示例中,getRate 操作接受了一个 getRateRequest 消息作为它的输入,并返回
一个 getRateResponse 消息作为它的输出。

<binding>
<binding> 对应于用特定的协议 -- 如 SOAP 或者 CORBA -- 来实现的 <portType> 。
绑定的类型属性必须对应定义过的 <portType> 的名称。因为 WSDL 是中性的协议,所
以您可以指定 SOAP、CORBA、DCOM 和其它的标准协议的绑定。如果一个服务支持不止一
个协议,WSDL 应该对每一个它支持的协议都包含一个 <binding>。
在示例中,<binding> 部分表明使用标准的 SOAP 编码进行的 RPC 到 HTTP 的通信。也
请注意 soapAction (在最后的部分描述)在这个示例中的设置是设置成空串,并且服
务的 URI 被设置成 "urn:xmethods-CurrencyExchange"。

<service>
一个<service> 是一个端口集,而 <port> 代表了在特定端点进行特定绑定的可用性。
端口的绑定属性必须对应于前面定义过的 <binding> 的名称。
在示例中,通过 Xmethods 网站的 CurrentExchangeBinding 绑定可访问 <service>。

<documentation>
任何 WSDL 元素可以声明一个可选的 <documentation> 元素,其中包含人们可读的关于
那个元素的信息。在示例中,唯一有文档描述的元素是 <service>。对于其他的元素,
例如独立操作,有文档描述也是很普通的。



使用 WSDL 来生成客户端存根

因为 WSDL 包含了对服务接口的完整描述,所以可以使用它来创建能简化服务访问的存
根。

IBM WSDL 工具包允许您为 Apache SOAP 创建存根。为了说明这个问题,让我们创建一
个客户端存根,它允许我们调用在 Xmethods 上建立的货币交换服务。首先,建立一个
\demo3 目录来存放这部分的所有软件。然后通过使用浏览器的 File, Save as 选项,
把示例CurrentExchange的 WSDL 文件保存到这个目录下。

然后通过键入下面的命令来建立客户端存根:
\demo3> java com.ibm.wsdl.Main -in CurrencyExchangeService.wsdl
这产生了一个叫做 CurrencyExchangePortTypeProxy.java 的客户端存根类(如清单 2
所示)。如果得到了 "unable to load JDK compiler" 的消息,您可以忽略掉,因为
我们将手动编译客户端存根了。

就象您能看到的,客户端的存根看上去就像我们在前面部分里使用过的代码。客户端程
序现在能够像常规的 Java 对象那样,使用代理服务类来访问 Web 服务(请参阅清单
3)。

清单 3:一个代理服务的客户端类 public class Client1
{
public static void main( String[] args ) throws Exception
{
CurrencyExchangePortTypeProxy exchange = new CurrencyExchangePortTypePro
xy();
float rate = exchange.getRate( "USA", "japan" );
System.out.println( "rate = " + rate );
}
}

如果您编译并运行这些文件,您应该能够看到在图 1 里的输出。

图 1:来自于 CurrencyExchangePortTypeProxy.java 的输出



生成 WSDL

大多数厂商的工具包包括某些从一个组件中自动生成 WSDL 的方法,其中包括 IBM WSTK
和 Microsoft .NET studio。为了说明 WSTK 如何允许从一个服务生成 WSDL,我将使
用在清单 4 中的天气服务。

清单 4:从一个组件中生成 WSDL public class Weather
{
public float getTemp( String zipcode )
{
System.out.println( "getTemp( " + zipcode + " )" );
return 56;
}
public void setTemp( String zipcode, float temp )
{
System.out.println( "setTemp( " + zipcode + ", " + temp + " )" );
}
}

为了给这个类创建 WSDL,首先要编译它,然后在 \demo3 目录里启动 WSTK 的 servic
eWizard。在尝试调用下面这条命令之前,要确保 \wstk-2.1\bin 在您的PATH路径设置
之中:
\demo3> serviceWizard
您应该看到如图 2 中的输出窗口。

图 2:Web 服务生成工具

单击 Next,就会提示您输入类的名字和它的类路径。对于这个示例,其它的地方设置成
默认值就可以了。图 3 显示了您如何填写各个字段。

图 3:Web 服务生成工具中的 WSDL 信息

当您单击 Next,会要求您选择希望通过 WSDL 来暴露什么方法。如图 4 所示,按住 S
hift 键选择所有的方法。

图 4:通过 WSDL 取出的所选方法

最后,您得到了一个摘要(在图 5 中),并且要按 Finish 来结束这个过程。

图 5: Web 服务创建工具摘要

恭喜您,您已经创建了您的第一个 WSDL 文件!事实上,您实际创建了两个文件:
Weather_Service-interface.wsdl:这个文件包括一个 WSDL 描述的 <message>、<por
tType> 和 <binding> 部分,它描述了 Web 服务的接口(请参阅清单 5)。
Weather_Service-impl.wsdl:这个文件定义了 WSDL 描述的 <service> 部分,然后导
入 Weather_Service-interface.wsdl(请参阅清单 6)。

这是很好的一个拆分,因为它减少了实现规范中的接口规范部分的重复。从理论上来说
,您可以有很多的 *impl.wsdl 文件对应一个 *interface.wsdl 文件,并且搜索像 UD
DI(下一个部分将会讨论)这样的注册表,来寻找与一个特定接口描述对应的一个或多
个实现。

一个有趣的问题是:<binding> 部分真的是属于 WSDL 接口文件?还是属于实现文件?
您可以认为它是属于实现文件,因为它是专门用于特别的绑定,例如 SOAP 或 CORBA。
我们只好等着看是否会有一个未来的标准来为此设定规范。

清单 5:Weather_Service-interface.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="Weather_Service-interface"
targetNamespace="http://www.weatherservice.com/Weather-interface
xmlns="http://schemas.xmlsoap.org/wsdl/
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/
xmlns:tns="http://www.weatherservice.com/Weather
xmlns:xsd="http://www.w3.org/1999/XMLSchema>
<message
name="IngetTempRequest">
<part name="meth1_inType1"
type="xsd:string"/>
</message>
<message
name="OutgetTempResponse">
<part name="meth1_outType"
type="xsd:float"/>
</message>
<message
name="InsetTempRequest">
<part name="meth2_inType1"
type="xsd:string"/>
<part name="meth2_inType2"
type="xsd:float"/>
</message>
<portType
name="Weather_Service">
<operation name="getTemp">
<input
message="IngetTempRequest"/>
<output
message="OutgetTempResponse"/>
</operation>
<operation
name="setTemp">
<input
message="InsetTempRequest"/>
</operation>
</portType>
<binding
name="Weather_ServiceBinding"
type="Weather_Service">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http/>
<operation
name="getTemp">
<soap:operation
soapAction="urn:weather-service"/>
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/
namespace="urn:weather-service"
use="encoded"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/
namespace="urn:weather-service"
use="encoded"/>
</output>
</operation>
<operation
name="setTemp">
<soap:operation
soapAction="urn:weather-service"/>
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/
namespace="urn:weather-service" use="encoded"/>
</input>
</operation>
</binding>
</definitions>

清单 6:Weather_Service-impl.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="Weather_Service"
targetNamespace="http://www.weatherservice.com/Weather
xmlns="http://schemas.xmlsoap.org/wsdl/
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/
xmlns:tns="http://www.weatherservice.com/Weather
xmlns:xsd="http://www.w3.org/1999/XMLSchema>
<service
name="Weather_Service">
<documentation>IBM WSTK 2.0 generated service definition file</documentati
on>
<port
binding="Weather_ServiceBinding"
name="Weather_ServicePort">
<soap:address
location="http://localhost:8080/soap/servlet/rpcrouter//>
</port>
</service><import
location="http://localhost:8080/wsdl/Weather_Service-interface.wsdl
namespace="http://www.weatherservice.com/Weather-interface>
</import>
</definitions>



从 WSTK WSDL 文件中生成存根

从 WSTK WSDL 文件来创建存根显得有一点复杂,由于 Weather_Service.impl 之中的导
入说明,使得 WSDL 存根产生器需到接口文件的位置执行HTTP Get。在这个示例中,它
是 http://localhost:8080/wsdl/Weather_Service-interface.wsdl。确保Tomcat 能
够为这个文件提供服务,在 Tomcat 的根文件夹 $TOMCAT_HOME\webapps\ROOT 中创建一
个目录 \wsdl,并把所有的 .wsdl 文件复制到这个目录中。然后,假定 tomcat 正运行
在 \demo3 目录下,在 \demo3 目录下键入以下命令:
\demo3> java com.ibm.wsdl.Main -in Weather_Service-impl.wsdl
Tomcat 将会从自己的 \wsdl 目录下提供 Weather_Service-interface.wsdl 文件,而
您应该会在 \demo3 目录下得到一个名叫 Weather_ServiceProxy.java 的存根类。清单
7 是一个测试客户端程序,它可以访问在您本地的 Tomcat 服务器上运行的天气服务:

清单 7:天气 Web 服务的测试代码 public class Client2
{
public static void main( String[] args ) throws Exception
{
Weather_ServiceProxy weather = new Weather_ServiceProxy();
float temp = weather.getTemp( "75248" );
System.out.println( "temp = " + temp );
weather.setTemp( "75248", 84 );
}
}

想要运行这个程序,编译 \demo3 目录下的所有 Java 文件,然后使用 Apache 配置屏
,用 URN urn:weather-service 来部署天气服务,然后执行客户端程序。图 6 显示了
当您填完所有的字段后,配置屏看上去的样子:

图 6:运行 Web 服务

当您运行程序时,客户端应该显示 getTemp() 的调用返回,而 Tomcat 窗口将会显示出
收到的 setTemp() 调用。


目标命名空间

由于 WSDL 文件能够导入其它的 WSDL 文件,因此总有可能发生名字冲突的时候。所以
,最晚拿到的 WSDL 文件需要在它们的 <definitions> 部分里面定义 targetNamespace
和 xml:tns 的属性,在那里 targetNamespace 被设成对应于特定的 WSDL(通常是原
始的 WSDL 文件的名称)的一个唯一 URL。完成此功能的 WSDL 生成器利用 tns: 对部
分与部分之间的引用做范围界定,来防止相同名字冲突。举一个示例,在清单 8 里,操
作声明使用 tns: 前缀,明确地把它使用的消息的范围界定在一个特定的 WSDL 文件中

图 8:使用 tns: 前缀
<portType name = "CurrencyExchangePortType">
<operation name = "getRate">
<input message = "tns:getRateRequest" name = "getRate"/>
<output message = "tns:getRateResponse" name = "getRateResponse"/>
</operation>
</portType>

很可能所有的 WSDL 工具包很快都会采用这种方法。



下一部分

在下一部分中,我们会了解 Web 服务怎样使用 UDDI (通用描述,发现和集成)来宣传
自己,以便让别的 Web 服务使用。我们将用新发布的 IBM UDDI4J 工具包在 UDDI 库里
发布和绑定 Web 服务。


参考资料
在第 1 部分中,Graham 详细介绍了构建 Web 服务应用以实现对等分布式网络的优势及
其面临的挑战。
在第 2 部分中,Graham 提供了如何开发 Web 服务的循序渐进的解释,其中包括您需要
什么工具,如何安装它们,如何编写代码以及如何配置服务。
在这个专栏的第 3 部分中,Graham 详细介绍了 SOAP 在线上交换信息的行为并解释了
它是怎样工作的。
请回顾第一个版本的 WSDL 1.0 规范。
请从 alphaWorks 下载 IBM 的 WSDL 工具包。
请从 alphaWorks 下载 IBM 的 Web 服务工具包。
Xmethods.net 有一个提供公共的 Web 服务的目录,这篇文章用到了其中的一个服务。


关于作者
Graham Glass (graham-glass@mindspring.com) 是 The Mind Electric 的创始人、CE
O 和首席设计师。该公司设计、构建和颁发许可证给前瞻性的分步式计算基础设施。他
相信,因特网的演变将反映出生物思维的演变,而协助人们和企业有效联网的体系结构
能帮助人们理解将人脑联结在一起的体系结构。
在创建 The Mind Electric 之前,Graham 是 ObjectSpace 的主席、CTO 和联合发起人
之一。该公司总部位于达拉斯,专门从事商家到商家的集成。Graham 还是 ObjectLess
on(一家提供前沿技术培训的公司)的创办人。他为 Prentice Hall 撰写了两本有关
UNIX 和 STL 的书籍,并以他对新兴技术的热情和清晰阐述而成为受欢迎的演说家。可
通过 graham-glass@mindspring.com 和他联系。
posted on 2007-12-10 15:31  番茄的梦想  阅读(4962)  评论(0编辑  收藏  举报