在创建 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,这是一个表示年份、月份和日期的 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/encoded)中使用这些引用的标准方法已被弃用,取而代之的是文档/文字(参见 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 文件中重用此定义。