级别: 初级 Graham Glass (graham-glass@mindspring.com), CEO/首席设计师, The Mind Electric
2001 年 2 月 01 日 这篇文章描述的是 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.jar (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/CurrencyExchangeService.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 CurrencyExchangePortTypeProxy();
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 的
serviceWizard 。在尝试调用下面这条命令之前,要确保
\wstk-2.1\bin 在您的PATH路径设置之中:
您应该看到如
图 2中的输出窗口。
图 2:Web 服务生成工具
单击
Next,就会提示您输入类的名字和它的类路径。对于这个示例,其它的地方设置成默认值就可以了。
图 3显示了您如何填写各个字段。
图 3:Web 服务生成工具中的 WSDL
信息
当您单击
Next ,会要求您选择希望通过 WSDL
来暴露什么方法。如
图 4所示,
按住 Shift
键选择所有的方法。
图 4:通过 WSDL
取出的所选方法
最后,您得到了一个摘要(在
图 5
中),并且要按
Finish来结束这个过程。
图 5: Web
服务创建工具摘要
恭喜您,您已经创建了您的第一个 WSDL
文件!事实上,您实际创建了
两个文件:
-
Weather_Service-interface.wsdl :这个文件包括一个
WSDL 描述的
<message> 、
<portType> 和
<binding> 部分,它描述了 Web
服务的接口(请参阅
清单 5)。
-
Weather_Service-impl.wsdl :这个文件定义了 WSDL
描述的
<service> 部分,然后导入
Weather_Service-interface.wsdl(请参阅
清单
6)。
这是很好的一个拆分,因为它减少了实现规范中的接口规范部分的重复。从理论上来说,您可以有很多的
*
impl.wsdl 文件对应一个 *
interface.wsdl
文件,并且搜索像
UDDI(下一个部分将会讨论)这样的注册表,来寻找与一个特定接口描述对应的一个或多个实现。
一个有趣的问题是:
<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</documentation>
<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 服务。
参考资料
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 在
第
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
的创始人、CEO
和首席设计师。该公司设计、构建和颁发许可证给前瞻性的分步式计算基础设施。他相信,因特网的演变将反映出生物思维的演变,而协助人们和企业有效联网的体系结构能帮助人们理解将人脑联结在一起的体系结构。
在创建 The Mind Electric 之前,Graham 是 ObjectSpace 的主席、CTO
和联合发起人之一。该公司总部位于达拉斯,专门从事商家到商家的集成。Graham
还是 ObjectLesson(一家提供前沿技术培训的公司)的创办人。他为
Prentice Hall 撰写了两本有关 UNIX 和 STL
的书籍,并以他对新兴技术的热情和清晰阐述而成为受欢迎的演说家。可通过
graham-glass@mindspring.com
和他联系。
|
对本文的评价
|