创建 Web 服务有两种开发方式:契约后置(Contract Last)和契约优先(Contract First)。当使用契约后置方法时,您从 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>此契约定义了一个接受日期的请求,这是一个表示年、月、日的 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,而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/编码)已被弃用,取而代之的是文档/文字(请参阅 WS-I 基本配置文件)。
这些只是处理 O/X 映射时的一些问题。在编写 Web 服务时,尊重这些问题很重要。尊重它们的最佳方式是完全专注于 XML,同时使用 Java 作为实现语言。这正是契约优先的全部意义所在。
除了上一节中提到的对象/XML 映射问题之外,还有其他原因偏爱契约优先的开发方式。
如前所述,契约后置开发方式会导致您的 Web 服务契约(WSDL 和您的 XSD)从您的 Java 契约(通常是一个接口)生成。如果您使用这种方法,您将无法保证契约随着时间的推移保持不变。每次您更改 Java 契约并重新部署时,Web 服务契约可能会随之发生变化。
此外,并非所有 SOAP 栈都从 Java 契约生成相同的 Web 服务契约。这意味着出于任何原因更改当前的 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 文件中重用此定义。