Greg Flurry , 高级技术人员, IBM
Manish Modh , 高级解决方案工程师, IBM
2005 年 12 月 05 日
引言
Web 服务提供了一种标准方法来实现可以远程调用的业务功能。它们将访问机制从实现中分离出来,从而可以支持互操作性。因此,Web 服务是实现 SOA(要求请求程序和提供程序之间进行松散耦合)的事实标准。工具开发行业也迅速地融入到 Web 服务的浪潮中,并为 Web 服务的开发提供了多种机制。这些机制现在已经形成了一套 Web 服务的开发模式,其中每一种模式都有自己的优缺点,这些优缺点是我们在特定情况下确定应该使用哪种模式的依据。
本文假定您基本熟悉 Web 服务的概念和标准。本文所使用的 Web 服务实现平台是 Java™,同时本文还假定您熟悉使用 Java 语言的 Web 服务,即 JAX-RPC,这是 Java Community Process 制定的两个规范。JAX-RPC 为 Java 2 Enterprise Edition (J2EE) 平台中的可部署、可执行 Web 服务定义了部署时构件和运行时构件。这些构件的例子包括:Web 服务描述语言(Web Services Definition Language,WSDL)文档和用于协助部署的 webservices.xml 文件。虽然内容比较简单,但 JAX-RPC 还是定义了一套请求程序端构件,其中包括一个用于定义客户端行为的 webservicesclient.xml 文件。尽管本文重点讨论的是 Java 平台,但文中描述的各种模式同样适用于任何一个支持 Web 服务开发的平台,例如 .NET。
SOA 的主要原则之一是发布、查找和绑定。该原则认为任何 Web 服务开发的关键之处在于 WSDL 文档,该文档定义了相应的 Web 服务实现的接口和绑定。WSDL 文档定义了 Web 服务请求程序和提供程序间的契约,以便两者的实现细节(甚至实现平台)可以各不相同,其中一方发生改变不会对另一方产生任何影响。
WSDL 文档有几方面与本文所讨论的内容有关。其中关系最大的是 types 和 portType 元素。types 元素为接口中所使用的数据类型定义了 XML 模式;这些类型可以显式定义在 WSDL 文档中,也可以从其他 XML 模式文件导入。portType 元素通过一组 operation 元素来标识 Web 服务所提供的操作集。每个 operation 元素依次引用(间接地通过 message 元素)定义在 types 元素中用作输入和输出参数的数据类型。
由于 WSDL 文档是如此的重要,因此本文将围绕如何创建和操作 WSDL 文档来描述 Web 服务的开发模式。在创建了 WSDL 文档后,您可以使用 Web 服务开发工具来帮助自己生成由 JAX-RPC 定义的必要部署时构件和运行时构件。请记住,WSDL 文档是 Web 服务开发的关键所在——本文认为 WSDL 文档位于开发的顶层,因为它是一种使用抽象的、独立于实现的术语来描述接口的元语言;认为代码位于开发的底层,因为通常可以生成、部署和执行代码。根据 WSDL 的术语,本文确定并描述了三种开发模式:
“自底向上”模式:从 Java 代码开始生成 WSDL 文档。 “自顶向下”模式:从 WSDL 文档开始生成 Java 代码。 “往返”模式:从 WSDL 文档开始生成 Java 代码,接着使用生成的 Java 代码来生成 WSDL 文档,然后使用生成的 WSDL 文档来生成 Java 代码。这些模式将在下面的部分中进行描述,其中包括了每种模式的优缺点。这些优缺点来源于在开发实际客户解决方案(包括业务集成和业务现代化)的过程中所积累的经验。
自底向上开发
面向 Java 的第一代 Web 服务工具的目标是:将现有的代码作为 Web 服务公开。该方法之所以称为“自底向上”,是因为它的起点是代码——这些代码被抽象成接口定义,并随后作为实现该接口的 Web 服务公开。这种模式已为许多 Web 服务方面的专业人员所熟知,即使最新的一代 Web 服务工具也都很好地支持这种模式。自底向上的开发模式包括以下步骤,如图 1 所示:
标识或创建 JavaBean 来代表服务接口输入和输出参数的数据类型。这些 JavaBean 可以是现有的 JavaBean,也可以是新建的 JavaBean。因为这些 JavaBean 常常用于在服务之间传输数据,所以有时也称其为数据传输对象(Data Transfer Object,DTO)。请注意,只有遵循 JavaBean 模式(例如,属性具有 getter 方法和 setter 方法)的属性才能在 Web 服务接口中公开。您可以使用标准 Java 开发工具来创建或修改所需的 JavaBean。
清单 1. 示例 DTO
package com.hello.dto;
public class HelloWorldData {
private String value;
public String getValue() {
return value;
}
public void setValue(String string) {
value = string;
}
}
标识或创建一个能够成为 Web 服务实现的 Java 类——无格式普通 Java 对象(Plain Old Java Object,POJO)或无状态会话 EJB。该 Java 类必须包括 Web 服务接口所公开的方法。这些方法必须使用步骤 1 中的 DTO 作为输入和输出参数。请注意,必须将实现 Web 服务中所公开的业务逻辑的方法标记为 public。此外,必要时您可以使用标准 Java 开发工具来创建或修改该 Java 类。该类不必遵循某些适用于 JavaBean 的规则。必要时该类可以在公开的方法中抛出任何异常。
清单 2. 示例 POJO 实现
package com.hello;
import com.hello.dto.*;
import com.hello.dto.*;
public class HelloWorld {
public String doIt(HelloWorldData input)
{
String retVal = null;
if (input != null)
{
retVal = input.getValue();
System.out.println("Input value: " + retVal);
}
return retVal;
}
}
使用 Web 服务工具从该 Java 类生成 Web 服务实现。有几个完善程度各不相同的候选工具,它们能够生成 JAX-RPC 在部署和使用 Web 服务的过程中所需的全部构件或部分构件。这些工具都能够:基于步骤 2 中的 Java 类的签名生成 WSDL 文档;为该 Java 类的公共方法所使用的每个数据类型 (JavaBean) 创建一个嵌入到该 WSDL 文档的 types 元素中的复杂类型定义;为该 Java 类的每个公共方法在 portType 元素中创建一个 operation 元素;生成将该实现部署为应用程序服务器上的 Web 服务所需的任何其他提供程序端构件。 这些工具中的低端工具包括有基于 Apache Axis 和 IBM WebSphere Application Server 中的 Java2WSDL 工具的命令行。这些低端工具只能生成主要的构件,例如 WSDL 文件和部署代码。高端工具的例子是 GUI 驱动的 Web 服务向导,它包含在企业应用程序开发工具的 IBM® Eclipse-based WebSphere® Studio 系列(如 Application Developer)中。该 Web 服务向导能够生成所有 JAX-RPC 构件和目标应用程序服务器的特定构件。实际上,如果需要,该向导甚至可以部署和启动 Web 服务。
清单 3. 在所生成的 HelloWorld.wsdl 的 types 元素内生成的 schema 元素
<schema elementFormDefault="qualified" targetNamespace="http://dto.hello.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:impl="http://hello.com" xmlns:intf="http://hello.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<complexType name="HelloWorldData">
<sequence>
<element name="value" nillable="true" type="xsd:string" />
</sequence>
</complexType>
</schema>
清单 4. Java2WSDL 为服务器端生成的构件列表
Java Resources/com/hello/HelloWorld_SEI.java - Service Endpoint Interface
Java Resources/com/hello/dto/HelloWorldData_Deser.jav - Deserialize DTO from XML message
Java Resources/com/hello/dto/HelloWorldData_Helperjava - Helper class that contains DTO metadata
Java Resources/com/hello/dto/HelloWorldData_Ser.java - Serialize DTO into XML message
WebContent/WEB-INF/wsdl/HelloWorld.wsdl - WSDL returned from ?wsdl HTTP request
WebContent/WEB-INF/webservices.xml - JAX-RPC server deployment descriptor
WebContent/WEB-INF/web.xml - Web services servlet definition
WebContent/WEB-INF/ibm-webservices-bnd.xmi - WebSphere-specific binding extensions
WebContent/WEB-INF/ibm-webservices-ext.xmi - WebSphere-specific extensions
WebContent/WEB-INF/HelloWorld_mapping.xml - WSDL to Java mapping metadata
WebContent/wsdl/com/hello/HelloWorld.wsdl - Copy of WSDL required by some runtimes such as process choreography
使用 Web 服务工具为该 Web 服务请求程序生成构件(使用步骤 3 中生成的 WSDL 文档)。这些工具能够:为每个定义在 WSDL 的 types 元素内的复杂类型生成一个 Java 类 (DTO);生成一个服务端点接口 (SEI),该接口含有 portType 元素内的每个操作的方法;生成一个存根来实现该 SEI,客户机可以使用该存根将请求发送到 Web 服务实现,并接收来自 Web 服务实现的响应。这些工具还可以生成其他构件,具体情况依赖于请求程序的性质(是否为 J2EE 容器)和工具本身。生成提供程序实现的同类工具通常还会有一个请求程序版本。因此,工具的范围相同,从 Apache Axis 或 Application Server 中的命令行驱动的 WSDL2Java 到 WebSphere Studio Application Developer 中的 Web 服务向导。
清单 5. Java2WSDL 为客户机端生成的构件列表
Java Resources/com/hello/HelloWorld.java - Service Endpoint Interface for client
Java Resources/com/hello/HelloWorldProxy.java - Client Proxy implementing interface
Java Resources/com/hello/HelloWorldService.java - Interface for client-side service locator
Java Resources/com/hello/HelloWorldServiceLocator.java - Implementation for service locator
Java Resources/com/hello/HelloWorldSoapBindingStub.java - Client-side Stub implementation
Java Resources/com/hello/dto/HelloWorldData.java - Client-side DTO implementation
Java Resources/com/hello/dto/HelloWorldData_Deser.java - Deserialize DTO from XML message
Java Resources/com/hello/dto/HelloWorldData_Helperjava - Helper class that contains DTO metadata
Java Resources/com/hello/dto/HelloWorldData_Ser.java - Serialize DTO into XML message
WebContent/WEB-INF/wsdl/HelloWorld.wsdl - WSDL for client request
WebContent/WEB-INF/webservicesclient.xml - JAX-RPC client deployment descriptor
WebContent/WEB-INF/ibm-webservicesclient-bnd.xmi - WebSphere-specific binding extensions
WebContent/WEB-INF/ibm-webservicesclient-ext.xmi - WebSphere-specific extensions
WebContent/WEB-INF/HelloWorld_mapping.xml - WSDL to Java mapping metadata
从步骤 1 到步骤 3 必须由服务提供程序执行。步骤 4 必须由服务请求程序执行。服务请求程序只能在服务提供程序提供 WSDL(在步骤 3 中)后执行步骤 4。请注意,前三步所描述的 WSDL 不可能成为一个标准的、甚至可以公开使用的 WSDL。然而,步骤 4 的执行情况确实就像 WSDL 来自公共来源(如服务注册中心)一样。
图 1. 自底向上的开发模式
评估自底向上方法
自底向上方法的显著优点包括:
能够快速地将遗留实现作为 Web 服务公开。 需要很少或根本不需要有关 WSDL 或 XML 的知识,因为 WSDL 文档可以由工具生成。 许多优秀的工具都支持这种方法。实际上,完善的工具不仅可以完成在提供程序端创建可部署、可执行的 Web 服务实现所需的全部工作,而且还可以完成允许在请求程序端通过请求访问该实现所需的全部工作。自底向上方法的缺点包括:
所生成的定义 WSDL 文档中的数据类型的模式只派生于提供程序环境中的 Java 类,而非派生于基于任何标准的模式(请参见上面的清单 3)。 提供程序端的数据类型不可能是简单的 DTO,因为它们还包括其他的业务逻辑。这样的业务逻辑无法在请求程序端重新建立。 所生成的模式被嵌入到 WSDL 中,这使得在其他 Web 服务的定义中重用该模式可能比较困难。当然,可以从原始的 WSDL 文档中提取该模式。 服务器端的 Web 服务实现的开发和客户端 Web 服务请求程序的开发不能并行进行。服务器端的框架和 DTO 必须在可以生成 WSDL 文档(可以用于生成客户端存根和 DTO)之前进行开发。 对接口的递增更改更加难于管理。例如,如果更改实现服务的类的接口并重新生成 WSDL,则该 WSDL 文档可能会发生比较重大的改变,从而导致无法与现有的客户机进行互操作。这主要是因为:服务器端认为实现服务的类是主接口,而客户端则认为服务器端提供的 WSDL 是主接口。这种分歧会导致两个接口不同步,而且难于调试和修复。 嵌入的模式类型的名称空间通常从服务器端 JavaBean 的 Java 包的名称生成。因此,如果该包的名称发生改变,名称空间就会相应地发生改变,这意味着类型不再是一致的。大多数工具支持包到名称空间的映射,但这必须在工具执行的过程中显式地进行设置。
自顶向下的开发
在自顶向下的开发过程中,客户端和服务器端的开发人员使用 WSDL(“顶部”)为各自的环境生成必要的构件(“底部”)。自顶向下的开发能够逐渐成为大家普遍接受的实践至少有两个重要的原因。第一个原因是,在许多情况下,描述服务的 WSDL 在某个注册中心(如 UDDI)中公开可用。借助目前可用的工具,不仅服务器端开发人员可以从 WSDL 开始定义由 WSDL 定义的 portType 元素的新实现,而且客户端开发人员也可以从相同的 WSDL 开始开发该服务的客户机。第二个原因(这甚至是一个更为重要的原因)是,该行业的趋势是使用 XML 模式定义 (XSD) 来定义可互操作的数据标准。这些使用 XSD 定义的行业标准数据类型可能会成为定义新的 Web 服务接口的理想选择。因此,自顶向下的开发模式从标识或开发与 Web 服务领域相关的 XML 模式开始,然后为该 Web 服务创建一个 WSDL 文档。在这种模式下,客户端和服务器端的开发可以并行启动。
自顶向下的开发模式包括以下开发步骤(如图 2 所示):
标识或创建一个与问题域(描述了 Web 服务操作的输入和输出数据类型)相关的 XML 模式。这些数据类型应该定义在一个或多个模式 (.xsd) 文件中。您可以使用标准的模式开发工具来创建或修改必要的模式。
清单 6. 示例 HelloWorldData.xsd
<?xml version="1.0" encoding="UTF-8"?>
<schema elementFormDefault="qualified" targetNamespace="http://dto.hello.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:impl="http://hello.com"
xmlns:intf="http://hello.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<complexType name="HelloWorldData">
<sequence>
<element name="value" nillable="true" type="xsd:string" />
</sequence>
</complexType>
</schema>
创建一个新的 WSDL 文件,其包含:一个 types 元素,该元素导入(但不包括)步骤 1 中的模式文件;一个 portType 元素,该元素又包含 operation 元素,用于引用定义在导入的模式中作为输入和输出参数的数据类型。创建该 WSDL 的推荐样式是包装的文档文本 (Wrapped-document Literal) 样式,该样式遵循 WS-I,用于最大限度地与 .NET 环境进行互操作。像 WebSphere Studio 系列一样完善的企业应用程序开发工具包括 WSDL 编辑器,该编辑器为用户创建 WSDL 文档提供了巨大的帮助。请参阅 Create Wrapped Document-Literal WSDL in WebSphere Studio Application Developer 以获得更多信息。
清单 7. 示例 HelloWorld.wsdl 中的 schema 元素(其中包含 import 元素)
<schema elementFormDefault="qualified" targetNamespace="http://hello.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:impl="http://hello.com"
xmlns:intf="http://hello.com" xmlns:tns2="http://dto.hello.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
<import namespace="http://dto.hello.com" schemaLocation="WorklistData.xsd"/>
生成提供程序端和请求程序端的 JAX-RPC 构件。请注意,下面描述的这些步骤可以由两个不同的独立团队并行完成。每一端的实现平台也可以不同。 使用 Web 服务工具生成提供程序端构件。工具的范围从 Apache Axis 或 WebSphere Application Server 中的命令行驱动的 WSDL2Java 到 WebSphere Studio 系列中的 Web 服务向导。这些工具为定义在 WSDL 的 types 元素中的每个复杂类型生成一个 Java 类 (DTO)。它们还生成一个 POJO 或一个无状态会话 EJB 框架服务实现,含有 portType 元素内的每个操作的方法。这些工具还可以生成 JAX-RPC 将该实现部署为应用程序服务器中的 Web 服务所需的任何其他提供程序端构件。这些构件的例子包括 webservices.xml 文件。还可能包括由工具生成的其他一些应用程序服务器依赖的元数据文件。在运行工具后,服务器端的开发人员必须实现正确的业务逻辑以执行该服务定义中的操作语义。
清单 8. WSDL2Java 为服务器端生成的构件列表
Java Resources/com/hello/HelloWorld.java - Service Endpoint Interface for server
Java Resources/com/hello/HelloWorldSoapBindingImpl.java - Server-side Stub implementation
Java Resources/com/hello/dto/HelloWorldData.java - Server-side DTO implementation
Java Resources/com/hello/dto/HelloWorldData_Deser.java - Deserialize DTO from XML message
Java Resources/com/hello/dto/HelloWorldData_Helperjava - Helper class that contains DTO metadata
Java Resources/com/hello/dto/HelloWorldData_Ser.java - Serialize DTO into XML message
WebContent/WEB-INF/wsdl/HelloWorld.wsdl - WSDL for client request
WebContent/WEB-INF/webservices.xml - JAX-RPC server deployment descriptor
WebContent/WEB-INF/ibm-webservices-bnd.xmi - WebSphere-specific binding extensions
WebContent/WEB-INF/ibm-webservices-ext.xmi - WebSphere-specific extensions
WebContent/WEB-INF/HelloWorld_mapping.xml - WSDL to Java mapping metadata
使用 Web 服务工具为服务请求程序从 WSDL 文档生成 DTO、SEI和客户机存根实现。该过程与上述自底向上模式中的步骤 4 完全相同。同前面一样,客户端开发人员必须使用存根调用客户端业务逻辑中的服务。请参见清单 5 中为客户端生成的示例构件。
图 2. 自顶向下的开发模式
评估自顶向下方法自顶向下方法的优点包括:
支持使用基于现有标准的 XSD 类型。请参见上面的清单 6 和清单 7 以获得相关示例。 在为当前的服务开发了新的模式类型后,只需将最新开发的 XSD 导入到其他服务中,就可以轻松地将其重用于其他服务。 允许客户端和服务器端并行地独立开发。 通过更改 WSDL 本身,最佳地管理服务的递增更改。由于 WSDL 是客户端和服务器端的公共接口(或契约),因此可以轻松地管理这些更改,而不影响与现有请求程序或提供程序的互操作性。 这些工具将使用定义在 WSDL 中的名称空间来确定生成的 JavaBean(或 DTO)的包名。大多数工具都支持名称空间到包名的映射。从 WSDL 开始开发 Web 服务的优点是,客户端和服务器端都可以使用不同的包名映射,而不对访问服务产生影响。自顶向下方法的缺点包括:
需要有关 WSDL 和 XSD 的知识,因为必须手动生成或操作这两者。即使利用现有的完善工具来生成 WSDL 和 XSD,也仍然需要详细了解 WSDL 和 XSD 的结构,以确保遵循标准并获得最佳性能。 支持自顶向下方法的工具通常要比支持自底向上方法的工具有更多的限制,但这种情况正在逐渐改进。例如,许多工具不能正确地处理导入模式而不是嵌入模式的 WSDL 文件,而且也缺少对生成包装的文档文本样式的 WSDL 的全自动支持。导入模式最常见的问题是,XDS 必须与 WSDL 在同一目录中,并且相对路径名始终无效。值得注意的是,您可以使用自底向上流程生成驱动自顶向下流程的 WSDL。例如,您可以使用自底向上流程创建发布在 UDDI 注册中心内的 WSDL。我们推荐您在进行上述操作时,使用工具将 WSDL 的 types 元素中的模式与该定义的其他部分分开,以便可以重用该模式。任何自顶向下方法中的要点是:一旦生成 WSDL(手动生成或者从代码中生成),该 WSDL 就会立刻成为请求程序和提供程序所使用的主接口。
往返开发
开发 Web 服务的两种主要方法是自顶向下和自底向上。某些开发人员还采取了第三种方法,这主要是因为工具都或多或少地存在一些缺点或不足,并且开发人员也缺乏对 Web 服务工具和技术的全面了解。在所谓的往返开发中,开发人员开始时使用一部分自底向上流程,接着再使用一部分自顶向下流程。首先,请遵循自顶向下方法中的步骤 1-3a,这主要是为了生成所谓的 DTO(可能根据基于标准的 XSD)。DTO 用于 POJO 或 EJB 的接口中,它是通过手动创建的,用于提供与 WSDL 文档中定义的 portType 操作相对应的公共方法。然后,请遵循自底向上方法中的步骤 2-4。跳过自顶向下方法中的步骤 3b 是因为自底向上方法中步骤 3 生成的 WSDL 用于客户端。整个流程如图 3 所示。
图 3. 往返模式,WSDL – Java – WSDL 的开发模式
评估往返方法
该方法的主要优点是,您可以利用这种方法来回避工具选取的问题,或弥补自己对 Web 服务工具和技术相关知识的不足。该方法允许通过更改代码而不是更改模式来对现有模式和 Web 服务进行自定义。如果工具不支持您所期望的形式(如 EJB)实现服务,您也可以使用该方法。
往返方法的缺点包括:
Java 的某些基本类型和模式类型不能很好地相互转换。请参阅 Web services programming tips and tricks: Roundtrip issues, an introduction 以获得更多信息。 该方法所需的其他步骤不必要地使开发流程变得复杂。 与原始的 WSDL 及其导入的模式相比,所生成的 WSDL 缺少可重用性。 原始的 WSDL 和最终输出的 WSDL 中的数据类型的名称空间可能因为“名称空间到包映射”和“包到名称空间映射”的不同而不同。服务器端的开发人员必须妥善处理这种情况。结束语
虽然开发人员已经实现了 Web 服务,但是他们很少遵循严格的开发方法。本文描述了三种开发模式,您可以借助目前可用的工具使用这些模式来开发 Web 服务。
自底向上模式是将现有功能公开为 Web 服务的最好方法。自顶向下模式不仅提供了最大的灵活性和可重用性,而且也是在开发新的 Web 服务时快速达到最佳实践状态的一种方法。往返模式可以在以上两种更好的方法无法使用时作为一种辅助方法。不过,根据我们在同实际客户接触的过程中的观察,我们建议开发人员首先从自顶向下方法开始,以保证开发出基于最佳标准且可扩展的接口。如果需要快速地将现有功能公开,请使用自底向上方法作为辅助方法。最后一点,请避免完全使用往返方法,因为该方法还存在某些不一致的地方和开发缺陷。
参考资料
学习
您可以参阅本文在 developerWorks 全球站点上的 英文原文
Create Wrapped Document-Literal WSDL in WebSphere Studio Application Developer:了解如何从头开始创建包装的文档文本样式的 WSDL。
Web services programming tips and tricks: Roundtrip issues, an introduction:了解更多有关往返模式的内容。
Web Services Interoperability:获得更多关于Web 服务互操作性(Web Services Interoperability)组织的信息。
获得产品和技术
Java API for XML-Based RPC:下载 Java API for XML-based RPC 规范。