前言

在当前面向服务架构(SOA)的时代,越来越多的人使用Web服务来连接以前未连接的系统。最初,Web服务被认为只是进行远程过程调用(RPC)的另一种方式。然而,随着时间的推移,人们发现RPC和Web服务之间存在很大的差异。特别是当与其他平台的互操作性很重要时,发送包含处理请求所需的所有数据的封装XML文档通常更为合适。从概念上讲,基于XML的Web服务更像消息队列,而不是远程解决方案。总的来说,XML应被视为数据的平台中立表示形式,即SOA的通用语言。在开发或使用Web服务时,重点应该放在XML上,而不是Java。spring-doc.cadn.net.cn

Spring Web Services 专注于创建这些以文档驱动的 Web 服务。Spring Web Services 促进了基于契约优先的 SOAP 服务开发,允许通过多种方式之一操作 XML 负载来创建灵活的 Web 服务。Spring-WS 提供了一个强大的消息分发框架,一个与您现有应用程序安全解决方案集成的WS-Security解决方案,以及一个遵循熟悉的 Spring 模板模式的客户端 APIspring-doc.cadn.net.cn

I. 简介

该参考文档的第一部分是 Spring Web Services 及其底层概念的概述。接着介绍了 Spring-WS,并解释了基于契约优先的 Web 服务开发的概念

1. 什么是 Spring Web Services?

1.1. 概述

Spring Web Services(Spring-WS)是Spring社区的产品,专注于创建文档驱动的Web服务。Spring Web Services旨在促进基于契约优先的SOAP服务开发,允许通过多种方式之一操作XML有效负载来创建灵活的Web服务。该产品基于Spring本身,这意味着您可以将Spring概念(例如依赖注入)作为Web服务的组成部分。spring-doc.cadn.net.cn

人们使用 Spring-WS 的原因有很多,但大多数人在发现其他 SOAP 栈在遵循 Web 服务最佳实践方面有所欠缺后,都会被它吸引。Spring-WS 让最佳实践变得易于实施。这包括诸如 WS-I 基本规范、契约优先开发以及在契约与实现之间保持松散耦合等实践。Spring Web Services 的其他关键特性还包括:spring-doc.cadn.net.cn

1.1.1. 强大的映射功能

您可以根据消息有效负载、SOAP Action头或XPath表达式,将传入的XML请求分发到任何对象。spring-doc.cadn.net.cn

1.1.2. XML API 支持

传入的 XML 消息不仅可以使用标准的 JAXP API(如 DOM、SAX 和 StAX)来处理,还可以使用 JDOM、dom4j、XOM,甚至是编组技术来处理。spring-doc.cadn.net.cn

1.1.3. 灵活的 XML 编组

Spring Web Services 基于 Spring 框架中的对象/XML映射模块,该模块支持 JAXB 1 和 2、Castor、XMLBeans、JiBX 以及 XStream。spring-doc.cadn.net.cn

1.1.4. 重用您的Spring专业知识

Spring-WS 使用 Spring 应用程序上下文进行所有配置,这应该有助于 Spring 开发人员快速上手。此外,Spring-WS 的架构与 Spring-MVC 类似。spring-doc.cadn.net.cn

1.1.5. 对 WS-Security 的支持

WS-Security允许您对SOAP消息进行签名、加密和解密,或者对它们进行身份验证。spring-doc.cadn.net.cn

1.1.6. 与 Spring Security 的集成

Spring Web Services 的 WS-Security 实现提供了与 Spring Security 的集成。这意味着您也可以为您的 SOAP 服务使用现有的 Spring Security 配置。spring-doc.cadn.net.cn

1.1.7. Apache 许可证

你可以放心地在你的项目中使用 Spring-WS。spring-doc.cadn.net.cn

1.2. 运行时环境

Spring Web Services 需要标准的 Java 8 运行时环境。Spring-WS 基于 Spring Framework 4.0.9 构建,但支持更高版本。spring-doc.cadn.net.cn

Spring-WS 包含多个模块,这些模块将在本节的其余部分中进行描述。spring-doc.cadn.net.cn

  • XML模块(spring-xml.jar)包含对Spring Web Services的各种XML支持类。该模块主要用于Spring-WS框架本身,而不是供Web服务开发人员使用。spring-doc.cadn.net.cn

  • 核心模块 (spring-ws-core.jar) 是 Spring 的 Web 服务功能的中心部分。它提供了核心的 WebServiceMessageSoapMessage 接口、服务器端 框架(具有强大的消息分发功能)、用于实现 Web 服务端点的各种支持类,以及 客户端WebServiceTemplatespring-doc.cadn.net.cn

  • 支持模块(spring-ws-support.jar)包含额外的传输方式(JMS、电子邮件等)。spring-doc.cadn.net.cn

  • The 安全 包 (spring-ws-security.jar) 提供了一个 WS-Security 实现,它与核心 Web 服务包集成。它允许您对 SOAP 消息进行签名、解密和加密,并添加主体Tokens。此外,它还允许您使用现有的 Spring Security 安全实现来进行身份验证和授权。spring-doc.cadn.net.cn

下图显示了 Spring-WS 模块之间的依赖关系。箭头表示依赖关系(即 Spring-WS 核心依赖于 Spring-XML 和 Spring 3 及更高版本中的 OXM 模块)。spring-doc.cadn.net.cn

spring deps

1.3. 支持的标准

Spring Web Services 支持以下标准:spring-doc.cadn.net.cn

2. 为什么选择契约优先?

当创建Web服务时,有两种开发方式:contract-last(后契约)和contract-first(先契约)。当使用contract-last方法时,您从Java代码开始,然后根据它生成Web服务契约(以WSDL形式——请参见侧边栏)。而使用contract-first时,您从WSDL契约开始,并使用Java来实现该契约。spring-doc.cadn.net.cn

什么是WSDL?spring-doc.cadn.net.cn

WSDL 代表 Web Service Description Language(Web服务描述语言)。WSDL 文件是一个 XML 文档,用于描述 Web 服务。它指定了服务的位置以及该服务所公开的操作(或方法)。有关 WSDL 的更多信息,请参阅 WSDL 规范spring-doc.cadn.net.cn

Spring-WS 只支持契约优先的开发风格,本节将解释原因。spring-doc.cadn.net.cn

2.1. 对象/XML 阻抗不匹配

类似于ORM领域,我们存在对象/关系阻抗不匹配的问题,将Java对象转换为XML也有类似的问题。乍一看,O/X映射问题似乎很简单:为每个要转换的Java对象创建一个XML元素,并将所有Java属性和字段转换为子元素或属性。然而,事情并不像表面上看起来那么简单,因为像XML(尤其是XSD)这样的分层语言与Java的图形模型之间存在着根本性的差异。spring-doc.cadn.net.cn

本节的大部分内容灵感来源于 [alpine][effective-enterprise-java]

2.1.1. XSD 扩展

在 Java 中,更改类行为的唯一方法是通过子类化它,将新行为添加到该子类中。在 XSD 中,您可以通过限制数据类型来扩展它——即约束元素和属性的有效值。例如,考虑以下示例:spring-doc.cadn.net.cn

<simpleType name="AirportCode">
  <restriction base="string">
      <pattern value="[A-Z][A-Z][A-Z]"/>
  </restriction>
</simpleType>

这种类型通过正则表达式限制了 XSD 字符串,仅允许三个大写字母。如果将此类型转换为 Java,我们最终会得到一个普通的 java.lang.String。正则表达式在转换过程中丢失了,因为 Java 不支持这类扩展。spring-doc.cadn.net.cn

2.1.2. 不可移植类型

web服务最重要的目标之一是实现互操作性:支持多种平台,例如 Java、.NET、Python 等。由于这些语言都有各自的类库,因此您必须使用某种通用的跨语言格式来进行它们之间的通信。这种格式就是 XML,它受到所有这些语言的支持。spring-doc.cadn.net.cn

由于这种转换,你必须确保在服务实现中使用可移植的类型。例如,考虑一个返回 java.util.TreeMap 的服务:spring-doc.cadn.net.cn

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结束,而这具有不同的语义。spring-doc.cadn.net.cn

这个问题在客户端开发时也同样存在。请看以下描述服务契约的 XSD 片段:spring-doc.cadn.net.cn

<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.Datejava.util.Calendar。然而,这些类实际上描述的是时间,而不是日期。因此,我们最终发送的数据实际上代表了2007年4月4日午夜(2007-04-04T00:00:00),这与2007-04-04并不相同。spring-doc.cadn.net.cn

2.1.3. 环形图

假设我们有以下的类结构:spring-doc.cadn.net.cn

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,最终会得到类似如下的内容:spring-doc.cadn.net.cn

<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>
                   ...

处理这样的结构可能需要很长时间才能完成,因为此循环没有停止条件。spring-doc.cadn.net.cn

解决此问题的一种方法是使用对已编组对象的引用:spring-doc.cadn.net.cn

<flight number="KL1117">
  <passengers>
    <passenger>
      <name>Arjen Poutsma</name>
      <flight href="KL1117" />
    </passenger>
    ...
  </passengers>
</flight>

这解决了递归问题,但引入了新的问题。首先,您无法使用 XML 验证器来验证此结构。另一个问题是,在 SOAP 中使用这些引用的标准方式(RPC/encoded)已被弃用,取而代之的是 document/literal(参见 WS-I 基本配置文件)。spring-doc.cadn.net.cn

这些都是在处理 O/X 映射时的一些问题。在编写 Web 服务时,尊重这些问题非常重要。最好的方式是完全专注于 XML,同时使用 Java 作为实现语言。这正是“契约优先”所关注的内容。spring-doc.cadn.net.cn

2.2. 合同优先与合同滞后

除了上一节提到的对象/XML映射问题之外,还有其他原因使得我们更倾向于选择契约优先的开发方式。spring-doc.cadn.net.cn

2.2.1. 脆弱性

正如前面提到的,采用“先实现后合约”的开发风格会导致您的Web服务合约(WSDL和XSD)根据您的Java合约(通常是一个接口)生成。如果您使用这种方法,则无法保证合约会随着时间的推移保持不变。每次更改并重新部署Java合约时,Web服务合约可能都会随之发生更改。spring-doc.cadn.net.cn

此外,并非所有SOAP栈都能从Java合约生成相同的Web服务合约。这意味着将您当前的SOAP栈更改为另一个(无论出于何种原因)也可能更改您的Web服务合约。spring-doc.cadn.net.cn

当 Web 服务合约发生更改时,必须指示该合约的用户获取新的合约,并可能更改其代码以适应合约中的任何更改。spring-doc.cadn.net.cn

要使契约有用,它必须尽可能长时间保持不变。如果契约发生更改,则需要联系服务的所有用户,并指示他们获取契约的新版本。spring-doc.cadn.net.cn

2.2.2. 性能

当一个 Java 对象被自动转换为 XML 时,无法确定通过网络发送了什么内容。一个对象可能引用另一个对象,而该对象又引用另一个对象,依此类推。最终,虚拟机堆中可能有一半的对象会被转换成 XML,从而导致响应时间变慢。spring-doc.cadn.net.cn

当使用契约优先时,您明确地描述了发送什么XML到何处,从而确保它正是您想要的内容。spring-doc.cadn.net.cn

2.2.3. 可重用性

在单独的文件中定义您的模式可以让您在不同的场景中重用该文件。考虑在一个名为 airline.xsd 的文件中定义 AirportCodespring-doc.cadn.net.cn

<simpleType name="AirportCode">
    <restriction base="string">
        <pattern value="[A-Z][A-Z][A-Z]"/>
    </restriction>
</simpleType>

您可以通过使用import语句在其他模式甚至WSDL文件中重用此定义。spring-doc.cadn.net.cn

2.2.4. 版本控制

即使合约必须尽可能长时间保持不变,但有时确实需要进行更改。在 Java 中,这通常会导致一个新的 Java 接口,例如 AirlineService2,以及该接口的(新)实现。当然,旧的服务必须保留,因为可能还有一些尚未迁移的客户端。spring-doc.cadn.net.cn

如果使用契约优先的方式,我们可以在契约和实现之间保持较松的耦合。这种较松的耦合允许我们在一个类中实现契约的两个版本。例如,我们可以使用 XSLT 样式表将任何“旧式”消息转换为“新式”消息。spring-doc.cadn.net.cn

3. 编写基于契约优先的Web服务

本教程展示了如何编写基于契约优先的Web服务——即如何开发以XML Schema或WSDL契约为起点,而将Java代码作为后续步骤的Web服务。Spring-WS专注于这种开发方式,本教程将帮助您入门。需要注意的是,本教程的第一部分几乎不包含特定于Spring-WS的信息,内容主要涉及XML、XSD和WSDL。教程的第二部分则重点介绍如何使用Spring-WS实现该契约。spring-doc.cadn.net.cn

The most important thing when doing contract-first web service development is tothink in terms of XML. This means that Java language concepts are of lesser importance. It is the XML that is sent across the wire, and you should focus on that. Java being used to implement the web service is an implementation detail.spring-doc.cadn.net.cn

在本教程中,我们定义了一个由人力资源部门创建的网络服务。客户端可以将请假申请表单发送到该服务以预订假期。spring-doc.cadn.net.cn

3.1. 消息

在本节中,我们重点关注发送到 Web 服务以及从 Web 服务接收的实际 XML 消息。我们首先确定这些消息的外观。spring-doc.cadn.net.cn

3.1.1. 假期

在该场景中,我们需要处理假期请求,因此确定假期在XML中的样子是有意义的:spring-doc.cadn.net.cn

<Holiday xmlns="http://mycompany.com/hr/schemas">
    <StartDate>2006-07-03</StartDate>
    <EndDate>2006-07-07</EndDate>
</Holiday>

假期由开始日期和结束日期组成。我们还决定对日期使用标准的ISO 8601日期格式,因为这样可以省去很多解析的麻烦。我们还为元素添加了命名空间,以确保我们的元素可以在其他XML文档中使用。spring-doc.cadn.net.cn

3.1.2. 员工

在场景中还存在