在创建 Web 服务时,有两种开发风格:契约滞后和契约优先。 使用契约滞后方法时,您从 Java 代码开始,并让 Web 服务契约(WSDL,参见侧栏)由此生成。使用契约优先时,您从 WSDL 契约开始,并使用 Java 来实现该契约。
Spring-WS 仅支持契约优先的开发风格,本节将解释原因。
与 ORM 领域类似,我们在那里有一个对象/关系阻抗不匹配,在将 Java 对象转换为 XML 时也存在类似的问题。乍一看,O/X 映射问题似乎很简单:为每个 Java 对象创建一个 XML 元素,将所有 Java 属性和字段转换为子元素或属性。然而,事情并非看起来那么简单:像 XML(尤其是 XSD)这样的分层语言与 Java 的图形模型之间存在根本区别[1]。
在 Java 中,更改类行为的唯一方法是将其子类化,并将新行为添加到该子类。在 XSD 中,您可以通过限制它来扩展数据类型:也就是说,约束元素和属性的有效值。例如,考虑以下示例
<simpleType name="AirportCode"> <restriction base="string"> <pattern value="[A-Z][A-Z][A-Z]"/> </restriction> </simpleType>
此类型通过正则表达式限制 XSD 字符串,仅允许三个大写字母。如果将此类型转换为 Java,我们将得到一个普通的 java.lang.String
;正则表达式在转换过程中丢失,因为 Java 不允许进行这些类型的扩展。
Web 服务最重要的目标之一是实现互操作性:支持 Java、.NET、Python 等多个平台。因为所有这些语言都有不同的类库,所以您必须使用一些通用的、跨语言的格式在它们之间进行通信。该格式是 XML,所有这些语言都支持它。
由于此转换,您必须确保在服务实现中使用可移植的类型。例如,考虑一个返回 java.util.TreeMap
的服务,如下所示
public Map getFlights() { // use a tree map, to make sure it's sorted TreeMap map = new TreeMap(); map.put("KL1117", "Stockholm"); ... return map; }
毫无疑问,此映射的内容可以转换为某种 XML,但由于没有在 XML 中描述映射的标准方法,因此它将是专有的。此外,即使它可以转换为 XML,许多平台也没有类似于 TreeMap
的数据结构。因此,当 .NET 客户端访问您的 Web 服务时,它可能会最终得到一个 System.Collections.Hashtable
,它具有不同的语义。
在客户端工作时也存在此问题。考虑以下描述服务契约的 XSD 代码段
<element name="GetFlightsRequest"> <complexType> <all> <element name="departureDate" type="date"/> <element name="from" type="string"/> <element name="to" type="string"/> </all> </complexType> </element>
此契约定义了一个接受 date 的请求,date 是一个表示年、月和日的 XSD 数据类型。如果我们从 Java 调用此服务,我们可能会使用 java.util.Date
或 java.util.Calendar
。然而,这两个类实际上描述的是时间,而不是日期。因此,我们实际上最终会发送代表 2007 年 4 月 4 日午夜的数据 (2007-04-04T00:00:00
),这与 2007-04-04
不同。
假设我们有以下简单的类结构
public class Flight { private String number; private List<Passenger> passengers; // getters and setters omitted } public class Passenger { private String name; private Flight flight; // getters and setters omitted }
这是一个循环图:Flight
引用 Passenger
,后者再次引用 Flight
。像这样的循环图在 Java 中很常见。如果我们采用一种幼稚的方法将其转换为 XML,我们将最终得到类似的东西
<flight number="KL1117"> <passengers> <passenger> <name>Arjen Poutsma</name> <flight number="KL1117"> <passengers> <passenger> <name>Arjen Poutsma</name> <flight number="KL1117"> <passengers> <passenger> <name>Arjen Poutsma</name> ...
这将花费很长时间才能完成,因为此循环没有停止条件。
解决此问题的一种方法是使用对已编组的对象的引用,如下所示
<flight number="KL1117"> <passengers> <passenger> <name>Arjen Poutsma</name> <flight href="KL1117" /> </passenger> ... </passengers> </flight>
这解决了递归问题,但也引入了新的问题。首先,您不能使用 XML 验证器来验证此结构。另一个问题是,在 SOAP (RPC/encoded) 中使用这些引用的标准方法已被弃用,转而支持 document/literal(请参阅 WS-I 基本配置文件)。
这些只是处理 O/X 映射时遇到的一些问题。在编写 Web 服务时,尊重这些问题非常重要。尊重它们的最佳方法是完全专注于 XML,同时使用 Java 作为实现语言。这就是契约优先的全部意义所在。
除了上一节中提到的对象/XML 映射问题之外,还有其他原因更倾向于契约优先的开发风格。
如前所述,契约滞后的开发风格导致您的 Web 服务契约(WSDL 和您的 XSD)从您的 Java 契约(通常是接口)生成。如果您使用这种方法,您将无法保证契约随着时间的推移保持不变。每次您更改 Java 契约并重新部署它时,都可能会对 Web 服务契约进行后续更改。
此外,并非所有的 SOAP 堆栈都会从 Java 契约生成相同的 Web 服务契约。这意味着将您当前的 SOAP 堆栈更改为不同的 SOAP 堆栈(无论出于何种原因),也可能会更改您的 Web 服务契约。
当 Web 服务契约更改时,必须指示契约的用户获取新契约,并可能更改他们的代码以适应契约中的任何更改。
为了使契约有用,它必须尽可能长时间地保持不变。如果契约更改,您将必须联系您的服务的所有用户,并指示他们获取该契约的新版本。
当 Java 自动转换为 XML 时,无法确定通过网络发送的内容。一个对象可能会引用另一个对象,后者引用另一个对象,等等。最终,虚拟机中堆上的一半对象可能会转换为 XML,这将导致响应时间变慢。
使用契约优先时,您可以明确描述在何处发送哪些 XML,从而确保它完全是您想要的。
在单独的文件中定义您的模式允许您在不同的场景中重用该文件。如果您在一个名为 airline.xsd
的文件中定义了一个 AirportCode,如下所示
<simpleType name="AirportCode"> <restriction base="string"> <pattern value="[A-Z][A-Z][A-Z]"/> </restriction> </simpleType>
您可以使用 import
语句在其他模式,甚至 WSDL 文件中重用此定义。