如何使用 WSDL 部署 Web 服务(详解): 第 1 部分
什么是 WSDL?
这种新的 .com 需要一种解决方案来描述它所提供的服务(Web 服务)。具体而言,这意味着您需要一种格式或某种类型的语法,使您可以通过使用它们来描述下列问题的答案:
您的在线业务提供什么服务?
您如何调用业务服务?
当用户调用您的业务服务时,该业务服务需要他/她提供什么信息?
用户将如何提供这些必需信息?
服务将以什么格式发送返回给用户的信息?
很幸运,WSDL 提供了完成所有这些作业的机制。
WSDL 和 SOAP
为更好理解 WSDL 是如何工作的,我将首先描述 SOAP 和 HTTP 是如何使用 WSDL 工作的。WSDL 的用途是“描述”您的 Web
服务。业务之间将通过交换 WSDL 文件来理解对方的服务。一旦知道您伙伴的服务并希望调用它们,SOAP 就派上用场了。可以将服务看作是通过
SOAP 访问的对象。
最有可能的情况是,您将通过因特网或电子邮件与潜在伙伴通信。当然,因特网使用 HTTP 而电子邮件以 SMTP 方式工作,这使得 HTTP 和 SMTP 成为作为 SOAP 的“传输服务提供者”的有利候选人。
WSDL 编写
现在,我将讲述为 Web 服务编写 WSDL 的过程。目的是公开现有的 Web 服务。您所处的情况也许就是下列情况之一:
您有一个现存的服务(例如,一个网站),并希望表示它的功能性。
您有一个 WSDL,并且希望依照已经决定表示的功能性来实现 Web服务器端的逻辑。(有些人也许会认为这是一个不可能的方案,但是 UDDI 的指纹概念使它变得极为可能;我将在本系列的第四部分讨论 UDDI)。
您正在从零开始,并且既无网站又无 WSDL 界面。
本文中所涵盖的信息适用于这些可能性中的任意一种或全部。
WSDL 编写的四个步骤
我将把 WSDL 编写分成四个简单步骤。遵循每个步骤,您的 Web 服务将准备就绪用于部署。
步骤 1:服务接口
您将构建一个移动电话销售公司的服务接口作为样本项目(我将这个服务称为 MobilePhoneService
)。该公司销售不同型号的移动电话,所以公司 Web 服务的后端数据存储库中将包含一个具有两列( model number 和 price
)的表格。(为了将焦点保持在 WSDL 本身,我保持该表格的简单性)。有两个关于要使用 WSDL 表示的服务的方法:
getListOfModels ()
getPrice (modelNumber)
GetListOfModels 方法提供了一个字符串数组,其中每个字符串表示一种移动电话的型号。 GetPrice
获得型号,然后返回它的价格。WSDL 将这些方法作为操作调用。现在将开始构建“WSDL 接口文件( WSDL interface file
)”。
每个 WSDL 文件的根元素都是 <definitions> ,必须在其中提供服务的完整描述。首先,必须在
<definitions> 元素中提供各种名称空间的声明。三个必须做的外部名称空间声明是 WSDL、SOAP 和 XSD(XML
模式定义)。还有一个名称空间 ― TNS,它指您的 MobilePhoneService(这表示 TNS(targetNamespace
的缩写)包含专为 MobilePhoneService 定义的所有元素和属性的名称)。但是 WSDL 是您将在 WSDL
编写中使用得最多的主要名称空间。在本系列文章中使用到其它名称空间时,我将提到它们的效用。
关于名称空间只要注意一点:WSDL 广泛地使用名称空间这一概念。我鼓励您到 W3C 的官方网站去学习关于名称空间的更多知识(请参阅
参考资料)。WSDL 是这种思想的一种实现,因为名称空间提供了无限的灵活性,而这恰恰是用于电子数据交换的可移植格式所需要的。
<definitions> 元素包含一个或多个 <portType> 元素,实际上,每个元素都是您希望表示的一系列
operation 。或者,您也可以将单个 portType
元素看作是将各种方法组成类的一个逻辑分组。例如,如果您的供应链管理解决方案需要在客户和供应商之间进行交互,您最可能做的是分别定义与他们交互的功能
性;也就是说,您将为用户和供应商各定义一个 portType。应该将每个 portType 称为 服务,因此整个 WSDL
文件将成为一个服务集合。
必须为每个服务提供一个名称。在本例中,仅有一个服务(因此只有一个 <portType> )。 需要使用该 portType 元素的 name 属性为移动电话销售服务指定名称。
在每个服务内可以有几个方法、或者 operation ,WSDL 通过 <operation>
元素来引用它们。样本应用程序有两个要表示的方法: getListOfModels 和 getPrice 。因此,您需要提供两个
<operation> 元素,每个元素有一个 name 。 我已经使用 <operation> 元素的 name
属性命名了每个操作。
此时,WSDL 文件看上去象 清单 1。
清单 1:定义操作
<?xml version="1.0" encoding="UTF-8"?>
2<definitions name="MobilePhoneService"
3 targetNamespace="www.mobilephoneservice.com/MobilePhoneService-interface"
4 xmlns="http://schemas.xmlsoap.org/wsdl/"
5 xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
6 xmlns:tns="http://www.mobilephoneservice.com/MobilePhoneService"
7 xmlns:xsd="http://www.w3.org/1999/XMLSchema">
8 <portType name="MobilePhoneService_port">
9 <operation name="getListOfModels ">
10 .
11 .
12 </operation>
13 <operation name="getPrice">
14 .
15 .
16 </operation>
17 </portType>
18</definitions>
步骤 2:指定参数
Listing 2: Defining parameters
2<definitions name="MobilePhoneService"
3targetNamespace="http://www.mobilephoneservice.com/MobilePhoneService-interface"
4xmlns="http://schemas.xmlsoap.org/wsdl/"
5xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
6xmlns:tns="http://www.mobilephoneservice.com/MobilePhoneService"
7xmlns:xsd="http://www.w3.org/1999/XMLSchema">
8<types>
9<xsd:schema targetNamespace="http://www.mobilephoneservice.com/MobilePhoneService"
10xmlns="http://www.w3.org/1999/XMLSchema/">
11<xsd:complexType name="Vector">
12<xsd:element name="elementData" type="xsd:String"/>
13<xsd:element name="elementCount" type="xsd:int"/>
14</xsd:complexType>
15</xsd:schema>
16</types>
17<message name="ListOfPhoneModels">
18<part name="models" type="tns:Vector">
19</message>
20<message name="PhoneModel">
21<part name="model" type="xsd:String">
22</message>
23<message name="PhoneModelPrice">
24<part name="price" type="xsd:String">
25</message>
26<portType name="MobilePhoneService_port">
27<operation name="getListOfModels ">
28<output message="ListOfPhoneModels"/>
29</operation>
30<operation name="getPrice">
31<Input message="PhoneModel"/>
32<output message="PhoneModelPrice"/>
33</operation>
34</portType>
35</definitions>
36
定义好操作(或方法)以后,现在需要指定将向它们发送和从它们返回的参数。在 WSDL 术语中,所有参数称为“消息”。认为您是在递送消息而结果得到返回的消息是有用的。方法调用是这样一种操作:它准备返回“消息”来响应进入的消息。
请回忆,在第一步骤中有两个操作要表示。第一个操作 getListOfModels 不必获得任何参数并且返回一个字符串数组,其中每个字符串表示移动电话的型号。因此,必须定义一个包含字符串数组的 <message> 元素。
看看 清单 2 中的各种 <message> 元素。其中的第一个元素有一个等于 ListOfPhoneModels
的名称属性(该消息的逻辑名称),以及名称为 models 的单个 <part> 元素,这意味着该 ListOfPhoneModels
是一个“只含有一个 part 的”消息,其中仅有的这个 part 的名称是“models”。消息可以有任意多个 part ―
只要为它们起不同的名称,以唯一标识。
我已包括了 <part> 元素的另一个属性,它就是 type 。将这个“type”属性当作 C++ 或 Java
中的数据类型。我已经将 models 的数据类型指定为 tns:Vector。(请回忆,我在 <definitions>
根元素中指定了一些名称空间,其中之一是 tns 。)这个类型即指 MobilePhoneService 名称空间。这意味着当编写 WSDL
时,您可以创建自己的名称空间。现在您也许会问两个逻辑问题:为什么?和怎么做?
要回答 为什么,让我们以 getListOfModels 操作返回的字符串数组为例。WSDL 使用 XML
模式定义(XSD)定义的一些原始数据类型(诸如 int、float、long、short、byte、string、Boolean
等等),并允许您直接使用它们,或者以这些原始数据类型构建复杂数据类型后,在消息中使用它们。这就是为什么当引用复杂数据类型时,您需要定义自己的名称
空间。在本例中,需要为 array of strings 构建一个复杂数据类型。
现在来看 怎么做问题,您将使用 XSD 创建自己的名称空间。为实现这个目的,我在 <types> 元素中使用了
xsd:complexType 元素用来定义称为 Vector 的数据类型。 Vector 使用两个原始数据类型:string(元素数据)和
Integer(元素计数)。因此, Vector 成为名称空间的一部分并可以通过别名 tns 来引用。
在 清单 2 中,我以类似的方式定义了另外两个消息 PhoneModel 和 PhoneModelPrice 。这两个消息只使用了 xsd 名称空间中的原始数据类型 string,因此您不必为使用它们而定义任何更复杂的数据类型。
您也许已经注意到当创建 <message> 元素时,没有指定这些消息是进入参数还是返回值。这是一个您将在
<portType> 元素内的 <operation> 元素中完成的工作。因此,正如您在 清单 2 中所看到的,我已经将
<input> 和 <output> 元素都添加到这两个操作中。每个 input
元素通过消息名来引用它并将它当作用户调用该操作时要提供的参数。类似地,每个 <output>
元素引用一个消息;它将该消息当作操作调用的返回值。
至今, 清单 2准确地限定了目前的讨论的范围。
步骤 3:消息传递和传输
我以一种抽象方式定义了操作和消息,而不考虑实现的细节。实际上,WSDL 的任务是定义或描述 Web 服务,然后提供一个对外部框架的引用来定义
WSDL 用户将如何实现这些服务。可以将这个框架当作 WSDL 抽象定义和它们的实现之间的“绑定( binding )”。
当前,最流行的绑定( binding )技术是使用简单对象访问协议(SOAP)。WSDL 将指定能够访问 Web 服务实际实现的 SOAP
服务器,并且从那时起 SOAP 的整个任务就是将用户从 WSDL 文件带到它的实现。SOAP 是本系列文章中下一部分的主题,所以我将暂时避免讨论
SOAP 细节而继续集中讲述 WSDL 编写。
WSDL 编写的第三个步骤是描述将 SOAP 与 WSDL 文件绑定到一起的过程。您将把 <binding> 元素包括到
<definitions> 元素内。这个 binding 元素应该有 name 和 type 属性。 name 将标识这个绑定而
type 将标识您希望与这个绑定相关联的 portType(一组操作)。在 清单 3 中,您会发现 <portType> 元素的
name 与 <binding> 元素的 type 属性值相匹配。
WSDL binding 元素包含您将用于绑定用途的外部技术的声明。因为正在使用 SOAP,所以这里将使用 SOAP 的名称空间。WSDL 术语中,对外部名称空间的使用称为 extensibility 元素。
在 清单 3 中,您将看见一个空的 <soap:binding/> 元素。该元素的用途是声明将把 SOAP 作为绑定和传输服务使用。
<soap:binding> 元素有两个属性:style 和 transport。style 是一个可选属性,它描述该绑定内操作的性质。transport 属性指定 HTTP 作为该绑定将使用的级别较低的传输服务。
SOAP 客户机将从 WSDL 文件中读取 SOAP 结构并与另一端的 SOAP 服务器协调,所以必须特别关注 interoperability 。我打算在本系列文章的第三部分详细讲述该问题。
在空的 <soap:binding/> 元素后面,有两个 WSDL <operation> 元素,分别表示步骤 1
的操作。每个 <operation> 元素提供各自操作的绑定细节。因此,我提供了另一个 extensibility 元素,即
<soap:operation/> (仍然是一个空元素,与它发生的那个操作相关)。该 <soap:operation/>
元素有一个 soapAction 属性,SOAP 客户机将使用该属性创建 SOAP 请求。
请回忆步骤 2 中, getListOfModels 操作只有输出而无任何输入。因此,必须为该操作提供一个 <output>
元素。该输出包含 <soap:body/> 元素(仍然是一个空元素,与它发生的那个操作相关)。SOAP 客户机需要该信息来创建
SOAP 请求。 <soap:body/> 的名称空间属性值应该与您将部署到 SOAP 服务器上的 service
的名称相对应,SOAP 服务器将在在本系列文章的下一部分中讲述。
您已几乎要完成步骤 3 了。只要将下一个操作复制到这个操作的后面,您将完成 清单 3。
步骤 4:概括
您已经生成了一个完整描述服务 interface 的 WSDL 文件。现在,WSDL 需要一个附加步骤来创建该 WSDL 文件的概要。WSDL
将该文件称为 implementation 文件,在本系列文章的第四部分中,当您在 UDDI 注册中心发布 Web 服务时,会使用它。请看 清单
4― 这个 WSDL 实现文件。它的主要特性如下:
除了
清单 4(实现文件)引用不同的 targetNamespace 去引用实现文件以外, <definitions> 根元素和 清单 3(WSDL 接口文件)中的完全相同。
有一个
<import> 元素,该元素引用 清单 3的接口文件(文件名 MobilePhoneService-interface.wsdl)和它的名称空间。
有一个
<service> 标记,其中有一个表示该服务的逻辑名 name 。在 service 元素内有一个引用在 清单 3中创建的 SOAP 绑定的 port 元素。
将 IBM 的 Web Services ToolKit(WSTK)用于 WSDL 编写
现在,Web 服务已经完全就绪用于部署。我已经展示了如何手工创建这些文件(使用象 emacs 这样的简单文本编辑器)。可以使用诸如 IBM 的
WSTK(请参阅 参考资料以获得该工具箱以及本文提到的其它参考资料的链接)之类的 Web 服务编写工具来生成相同的这些文件。
WSTK 可以使用向导帮助过程来生成这些文件。用户可以生成与我在以上教程中演示的同样两种方法的 WSDL 文件,并将 WSTK 文件和 清单 3和 4中的 WSDL 文件作比较。
您将注意到下列差异:
WSTK
依照逻辑规则创建了所有名称属性;在本示例中,我使用了自己视为方便的名称。
WSTK 为每个操作至少生成一个 input
标记,即使该操作不必获得任何输入。 listAllPhoneModels 操作没有任何 input 元素,但是如果使用 WSTK 生成相同文件,它将因为包含这个方法的一个空 input 元素。
WSTK 产生了除已生成的两个文件以外的第三个文件。这第三个文件是
SOAP 引擎用于服务部署的 SOAP 部署描述符。我将在本系列文章中讨论服务部署。
在这部分中,我演示了手工进行 WSDL 编写以创建接口和实现文件,并与 IBM 的 Web Services ToolKit 生成的文件作了比较。在本系列的下一部分中,我将讨论在 SOAP 服务器上部署这个 WSDL 服务。