前言

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

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

I. 引言

参考文档的第一部分是对 Spring Web 服务和底层概念的概述。然后介绍了 Spring-WS,并解释了契约优先 Web 服务开发背后的概念

1. 什么是 Spring Web 服务?

1.1. 简介

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

人们使用 Spring-WS 的原因有很多,但大多数人在发现在遵循 Web 服务最佳实践时缺乏替代 SOAP 堆栈后,才被它所吸引。Spring-WS 使最佳实践成为一种简单的实践。这包括 WS-I 基本配置文件、契约优先开发以及契约和实现之间的松散耦合等实践。Spring Web 服务的其他主要功能是:spring-doc.cadn.net.cn

1.1.1. 强大的映射

您可以将传入的 XML 请求分发到任何对象,具体取决于消息负载、SOAP Action 标头或 XPath 表达式。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 服务构建在 Spring 框架中的 Object/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 服务的 WS-Security 实现提供了与 Spring Security 的集成。这意味着您也可以将现有的 Spring Security 配置用于 SOAP 服务。spring-doc.cadn.net.cn

1.1.7. Apache 许可证

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

1.2. 运行环境

Spring Web 服务需要标准的 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 服务的各种 XML 支持类。该模块主要面向 Spring-WS 框架本身,而不是 Web 服务开发人员。spring-doc.cadn.net.cn

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

  • Support 模块 (spring-ws-support.jar) 包含其他传输方式 (JMS、电子邮件等)。spring-doc.cadn.net.cn

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

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

Spring DEPS

1.3. 支持的标准

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

2. 为什么先签订合同?

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

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

WSDL 代表 Web 服务描述语言。WSDL 文件是描述 Web 服务的 XML 文档。它指定服务的位置和服务公开的作(或方法)。有关 WSDL 的更多信息,请参阅 WSDL 规范spring-doc.cadn.net.cn

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

2.1. Object/XML 阻抗不匹配

与 ORM 领域类似,我们遇到了 Object/Relational 阻抗不匹配的情况,将 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>

此 Contract 定义了一个请求,该请求采用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,它指的是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/编码)已被弃用,取而代之的是 document/literal(参见 WS-I 基本配置文件)。spring-doc.cadn.net.cn

这些只是处理 O/X 映射时的一些问题。在编写 Web 服务时,尊重这些问题很重要。尊重它们的最佳方法是完全关注 XML,同时使用 Java 作为实现语言。这就是合同优先的意义所在。spring-doc.cadn.net.cn

2.2. 合约优先与合约后

除了上一节中提到的 Object/XML Mapping 问题外,还有其他原因使您更喜欢 Contract 优先的开发风格。spring-doc.cadn.net.cn

2.2.1. 脆弱性

如前所述,契约最后的开发风格导致 Web 服务契约(WSDL 和 XSD)从 Java 契约(通常是一个接口)生成。如果使用此方法,则无法保证协定在一段时间内保持不变。每次更改 Java 协定并重新部署它时,Web 服务协定可能会发生后续更改。spring-doc.cadn.net.cn

此外,并非所有 SOAP 堆栈都从 Java 协定生成相同的 Web 服务协定。这意味着将当前的 SOAP 堆栈更改为不同的 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

使用 contract-first 时,您可以明确描述将哪些 XML 发送到何处,从而确保它正是您想要的。spring-doc.cadn.net.cn

2.2.3. 可重用性

通过在单独的文件中定义架构,您可以在不同场景中重复使用该文件。考虑一下AirportCode在名为airline.xsd:spring-doc.cadn.net.cn

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

您可以在其他模式甚至 WSDL 文件中重用此定义,方法是使用import陈述。spring-doc.cadn.net.cn

2.2.4. 版本控制

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

如果使用 contract-first,我们可以在 Contract 和 implementation 之间有一个更松散的耦合。这种更松散的耦合让我们可以在一个类中实现两个版本的 Contract。例如,我们可以使用 XSLT 样式表将任何 “旧式” 消息转换为 “新式” 消息。spring-doc.cadn.net.cn

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

本教程将介绍如何编写合同优先的 Web 服务,即如何开发首先以 XML 架构或 WSDL 合同开头,然后以 Java 代码开头的 Web 服务。Spring-WS 侧重于这种开发风格,本教程应该可以帮助您入门。请注意,本教程的第一部分几乎没有 Spring-WS 特定的信息。它主要与 XML、XSD 和 WSDL 有关。第二部分侧重于使用 Spring-WS 实现此 Contract 。spring-doc.cadn.net.cn

在进行契约优先的 Web 服务开发时,最重要的事情是从 XML 的角度进行考虑。这意味着 Java 语言概念不太重要。它是通过网络发送的 XML,您应该关注这一点。用于实现 Web 服务的 Java 是一个实现细节。spring-doc.cadn.net.cn

在本教程中,我们将定义一个由 Human Resources 部门创建的 Web 服务。客户可以向该服务发送假期申请表以预订假期。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. 员工

该方案中还有 employee 的概念。下面是它在 XML 中的样子:spring-doc.cadn.net.cn

<Employee xmlns="http://mycompany.com/hr/schemas">
    <Number>42</Number>
    <FirstName>Arjen</FirstName>
    <LastName>Poutsma</LastName>
</Employee>

我们使用了与以前相同的命名空间。如果此<Employee/>元素在其他场景中使用,则使用不同的命名空间可能是有意义的,例如http://example.com/employees/schemas.spring-doc.cadn.net.cn

3.1.3. HolidayRequest

holiday元素和employee元素可以放入<HolidayRequest/>:spring-doc.cadn.net.cn

<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
    <Holiday>
        <StartDate>2006-07-03</StartDate>
        <EndDate>2006-07-07</EndDate>
    </Holiday>
    <Employee>
        <Number>42</Number>
        <FirstName>Arjen</FirstName>
        <LastName>Poutsma</LastName>
    </Employee>
</HolidayRequest>

这两个元素的顺序无关紧要:<Employee/>可能是第一个元素。重要的是所有数据都在那里。事实上,数据是唯一重要的东西:我们采用数据驱动的方法。spring-doc.cadn.net.cn

3.2. 数据合约

现在我们已经看到了一些可以使用的 XML 数据示例,将其形式化为架构是有意义的。此数据协定定义了我们接受的消息格式。有四种不同的方法可以为 XML 定义这样的 Contract:spring-doc.cadn.net.cn

DTD 的命名空间支持有限,因此它们不适合 Web 服务。Relax NG 和 Schematron 比 XML Schema 更容易。遗憾的是,它们并未得到跨平台的广泛支持。因此,我们使用 XML Schema。spring-doc.cadn.net.cn

到目前为止,创建 XSD 的最简单方法是从示例文档中推断它。任何好的 XML 编辑器或 Java IDE 都提供此功能。基本上,这些工具使用一些示例 XML 文档来生成验证所有文档的架构。最终结果当然需要打磨,但这是一个很好的起点。spring-doc.cadn.net.cn

使用前面描述的示例,我们最终会得到以下生成的架构:spring-doc.cadn.net.cn

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        elementFormDefault="qualified"
        targetNamespace="http://mycompany.com/hr/schemas"
        xmlns:hr="http://mycompany.com/hr/schemas">
    <xs:element name="HolidayRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="hr:Holiday"/>
                <xs:element ref="hr:Employee"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="Holiday">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="hr:StartDate"/>
                <xs:element ref="hr:EndDate"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="StartDate" type="xs:NMTOKEN"/>
    <xs:element name="EndDate" type="xs:NMTOKEN"/>
    <xs:element name="Employee">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="hr:Number"/>
                <xs:element ref="hr:FirstName"/>
                <xs:element ref="hr:LastName"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="Number" type="xs:integer"/>
    <xs:element name="FirstName" type="xs:NCName"/>
    <xs:element name="LastName" type="xs:NCName"/>
</xs:schema>

此生成的架构可以改进。首先要注意的是,每个类型都有一个根级元素声明。这意味着 Web 服务应该能够接受所有这些元素作为数据。这是不可取的:我们只想接受<HolidayRequest/>.通过删除 wrapping element 标签(从而保留类型)并内联结果,我们可以完成此作,如下所示:spring-doc.cadn.net.cn

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:hr="http://mycompany.com/hr/schemas"
        elementFormDefault="qualified"
        targetNamespace="http://mycompany.com/hr/schemas">
    <xs:element name="HolidayRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Holiday" type="hr:HolidayType"/>
                <xs:element name="Employee" type="hr:EmployeeType"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:complexType name="HolidayType">
        <xs:sequence>
            <xs:element name="StartDate" type="xs:NMTOKEN"/>
            <xs:element name="EndDate" type="xs:NMTOKEN"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="EmployeeType">
        <xs:sequence>
            <xs:element name="Number" type="xs:integer"/>
            <xs:element name="FirstName" type="xs:NCName"/>
            <xs:element name="LastName" type="xs:NCName"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

该架构仍然存在一个问题:对于这样的架构,您可以预期以下消息会进行验证:spring-doc.cadn.net.cn

<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
    <Holiday>
        <StartDate>this is not a date</StartDate>
        <EndDate>neither is this</EndDate>
    </Holiday>
    PlainText Section qName:lineannotation level:4, chunks:[<, !-- ... --, >] attrs:[:]
</HolidayRequest>

显然,我们必须确保开始和结束日期确实是日期。XML Schema 具有出色的内置datetype 中。我们还将NCNames 更改为string实例。最后,我们将sequence<HolidayRequest/>all.这告诉 XML 解析器,命令的<Holiday/><Employee/>不显著。我们最终的 XSD 现在如下面的清单所示:spring-doc.cadn.net.cn

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:hr="http://mycompany.com/hr/schemas"
        elementFormDefault="qualified"
        targetNamespace="http://mycompany.com/hr/schemas">
    <xs:element name="HolidayRequest">
        <xs:complexType>
            <xs:all>
                <xs:element name="Holiday" type="hr:HolidayType"/> (1)
                <xs:element name="Employee" type="hr:EmployeeType"/> (1)
            </xs:all>
        </xs:complexType>
    </xs:element>
    <xs:complexType name="HolidayType">
        <xs:sequence>
            <xs:element name="StartDate" type="xs:date"/> (2)
            <xs:element name="EndDate" type="xs:date"/> (2)
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="EmployeeType">
        <xs:sequence>
            <xs:element name="Number" type="xs:integer"/>
            <xs:element name="FirstName" type="xs:string"/> (3)
            <xs:element name="LastName" type="xs:string"/> (3)
        </xs:sequence>
    </xs:complexType>
</xs:schema>
1 all告诉 XML 解析器,的<Holiday/><Employee/>不显著。
2 我们使用xs:date数据类型(由年、月和日组成)<StartDate/><EndDate/>.
3 xs:string用于名字和姓氏。

我们将此文件存储为hr.xsd.spring-doc.cadn.net.cn

3.3. 服务合同

服务协定通常表示为 WSDL 文件。请注意,在 Spring-WS 中,不需要手动编写 WSDL。基于 XSD 和一些约定,Spring-WS 可以为您创建 WSDL,如标题为“实现端点”的部分所述。本节的其余部分将介绍如何手动编写 WSDL。您可能希望跳到下一部分spring-doc.cadn.net.cn

我们从标准序言开始 WSDL,并导入现有的 XSD。为了将架构与定义分开,我们为 WSDL 定义使用单独的名称空间:http://mycompany.com/hr/definitions.以下清单显示了 preamble:spring-doc.cadn.net.cn

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:schema="http://mycompany.com/hr/schemas"
                  xmlns:tns="http://mycompany.com/hr/definitions"
                  targetNamespace="http://mycompany.com/hr/definitions">
    <wsdl:types>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <xsd:import namespace="http://mycompany.com/hr/schemas" schemaLocation="hr.xsd"/>
        </xsd:schema>
    </wsdl:types>

接下来,我们根据编写的架构类型添加消息。我们只有一条消息,即<HolidayRequest/>我们放入 schema:spring-doc.cadn.net.cn

    <wsdl:message name="HolidayRequest">
        <wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>
    </wsdl:message>

我们将消息作为作添加到端口类型中:spring-doc.cadn.net.cn

    <wsdl:portType name="HumanResource">
        <wsdl:operation name="Holiday">
            <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>
        </wsdl:operation>
    </wsdl:portType>

该消息完成了 WSDL 的抽象部分(就像接口一样)并留下了具体部分。混凝土部分由binding(告诉客户端如何调用您刚刚定义的作)和service(告诉客户端在何处调用它)。spring-doc.cadn.net.cn

添加混凝土部分是非常标准的。为此,请参阅您之前定义的抽象部分,确保使用document/literal对于soap:binding元素 (rpc/encoded已弃用),请选择soapAction对于作(在本例中为http://mycompany.com/RequestHoliday,但任何 URI 都有效),并确定location您希望请求到达的 URL(在本例中为http://mycompany.com/humanresources):spring-doc.cadn.net.cn

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:schema="http://mycompany.com/hr/schemas"
                  xmlns:tns="http://mycompany.com/hr/definitions"
                  targetNamespace="http://mycompany.com/hr/definitions">
    <wsdl:types>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <xsd:import namespace="http://mycompany.com/hr/schemas"              (1)
                schemaLocation="hr.xsd"/>
        </xsd:schema>
    </wsdl:types>
    <wsdl:message name="HolidayRequest">                                         (2)
        <wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>       (3)
    </wsdl:message>
    <wsdl:portType name="HumanResource">                                         (4)
        <wsdl:operation name="Holiday">
            <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>     (2)
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="HumanResourceBinding" type="tns:HumanResource">          (4)(5)
        <soap:binding style="document"                                           (6)
            transport="http://schemas.xmlsoap.org/soap/http"/>                   (7)
        <wsdl:operation name="Holiday">
            <soap:operation soapAction="http://mycompany.com/RequestHoliday"/>   (8)
            <wsdl:input name="HolidayRequest">
                <soap:body use="literal"/>                                       (6)
            </wsdl:input>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="HumanResourceService">
        <wsdl:port binding="tns:HumanResourceBinding" name="HumanResourcePort">  (5)
            <soap:address location="http://localhost:8080/holidayService/"/>     (9)
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>
1 我们导入 Data Contract 中定义的 schema。
2 我们定义了HolidayRequest消息,该消息在portType.
3 HolidayRequesttype 在 schema 中定义。
4 我们定义了HumanResourceport 类型,该类型在binding.
5 我们定义了HumanResourceBinding绑定,该绑定在port.
6 我们使用 document/literal 样式。
7 文本http://schemas.xmlsoap.org/soap/http表示 HTTP 传输。
8 soapAction属性表示SOAPAction将随每个请求一起发送的 HTTP 标头。
9 http://localhost:8080/holidayService/address 是可以调用 Web 服务的 URL。

前面的清单显示了最终的 WSDL。我们将在下一节中介绍如何实现生成的架构和 WSDL。spring-doc.cadn.net.cn

3.4. 创建项目

在本节中,我们使用 Maven 为我们创建初始项目结构。这样做不是必需的,但大大减少了我们设置 HolidayService 所必须编写的代码量。spring-doc.cadn.net.cn

以下命令使用 Spring-WS 原型(即项目模板)为我们创建一个 Maven Web 应用程序项目:spring-doc.cadn.net.cn

mvn archetype:create -DarchetypeGroupId=org.springframework.ws \
  -DarchetypeArtifactId=spring-ws-archetype \
  -DarchetypeVersion= \
  -DgroupId=com.mycompany.hr \
  -DartifactId=holidayService

前面的命令将创建一个名为holidayService.此目录中有一个src/main/webapp目录中,其中包含 WAR 文件的根目录。您可以找到标准 Web 应用程序部署描述符 ('WEB-INF/web.xml'),它定义了一个 Spring-WSMessageDispatcherServlet并将所有传入请求映射到此 servlet:spring-doc.cadn.net.cn

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">

    <display-name>MyCompany HR Holiday Service</display-name>

    <!-- take special notice of the name of this servlet -->
    <servlet>
        <servlet-name>spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-ws</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

除了前面的WEB-INF/web.xml文件中,您还需要另一个特定于 Spring-WS 的配置文件,名为WEB-INF/spring-ws-servlet.xml.此文件包含所有特定于 Spring-WS 的 bean,例如EndPointsWebServiceMessageReceivers并用于创建新的 Spring 容器。此文件的名称派生自随之而来的 Servlet 的名称(在本例中为'spring-ws') 替换为-servlet.xml附加到它。因此,如果您定义MessageDispatcherServlet替换为名称'dynamite',则特定于 Spring-WS 的配置文件的名称将变为WEB-INF/dynamite-servlet.xml.spring-doc.cadn.net.cn

(您可以看到WEB-INF/spring-ws-servlet.xml文件spring-doc.cadn.net.cn

创建项目结构后,您可以将上一节中的模式和 WSDL 放入'WEB-INF/'文件夹。spring-doc.cadn.net.cn

3.5. 实现端点

在 Spring-WS 中,您可以实现端点来处理传入的 XML 消息。端点通常是通过使用@Endpoint注解。在此终端节点类中,您可以创建一个或多个处理传入请求的方法。方法签名可以非常灵活。您可以包含几乎任何类型的与传入 XML 消息相关的参数类型,正如我们在本章后面解释的那样。spring-doc.cadn.net.cn

3.5.1. 处理 XML 消息

在这个示例应用程序中,我们使用 JDom 2 来处理 XML 消息。我们还使用 XPath,因为它允许我们选择 XML JDOM 树的特定部分,而无需严格的模式一致性。spring-doc.cadn.net.cn

下面的清单显示了定义我们的 holiday 端点的类:spring-doc.cadn.net.cn

package com.mycompany.hr.ws;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;

import com.mycompany.hr.service.HumanResourceService;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;

@Endpoint                                                                                     (1)
public class HolidayEndpoint {

    private static final String NAMESPACE_URI = "http://mycompany.com/hr/schemas";

    private XPathExpression<Element> startDateExpression;

    private XPathExpression<Element> endDateExpression;

    private XPathExpression<Element> firstNameExpression;

    private XPathExpression<Element> lastNameExpression;

    private HumanResourceService humanResourceService;

    @Autowired                                                                                (2)
    public HolidayEndpoint(HumanResourceService humanResourceService) throws JDOMException {
        this.humanResourceService = humanResourceService;

        Namespace namespace = Namespace.getNamespace("hr", NAMESPACE_URI);
        XPathFactory xPathFactory = XPathFactory.instance();
        startDateExpression = xPathFactory.compile("//hr:StartDate", Filters.element(), null, namespace);
        endDateExpression = xPathFactory.compile("//hr:EndDate", Filters.element(), null, namespace);
        firstNameExpression = xPathFactory.compile("//hr:FirstName", Filters.element(), null, namespace);
        lastNameExpression = xPathFactory.compile("//hr:LastName", Filters.element(), null, namespace);
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "HolidayRequest")                      (3)
    public void handleHolidayRequest(@RequestPayload Element holidayRequest) throws Exception {(4)
        Date startDate = parseDate(startDateExpression, holidayRequest);
        Date endDate = parseDate(endDateExpression, holidayRequest);
        String name = firstNameExpression.evaluateFirst(holidayRequest).getText() + " " + lastNameExpression.evaluateFirst(holidayRequest).getText();

        humanResourceService.bookHoliday(startDate, endDate, name);
    }

    private Date parseDate(XPathExpression<Element> expression, Element element) throws ParseException {
        Element result = expression.evaluateFirst(element);
        if (result != null) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            return dateFormat.parse(result.getText());
        } else {
            throw new IllegalArgumentException("Could not evaluate [" + expression + "] on [" + element + "]");
        }
    }

}
1 The HolidayEndpoint is annotated with @Endpoint. This marks the class as a special sort of @Component, suitable for handling XML messages in Spring-WS, and also makes it eligible for suitable for component scanning.
2 The HolidayEndpoint requires the HumanResourceService business service to operate, so we inject the dependency in the constructor and annotate it with @Autowired. Next, we set up XPath expressions by using the JDOM2 API. There are four expressions: //hr:StartDate for extracting the <StartDate> text value, //hr:EndDate for extracting the end date, and two for extracting the names of the employee.
3 The @PayloadRoot annotation tells Spring-WS that the handleHolidayRequest method is suitable for handling XML messages. The sort of message that this method can handle is indicated by the annotation values. In this case, it can handle XML elements that have the HolidayRequest local part and the http://mycompany.com/hr/schemas namespace. More information about mapping messages to endpoints is provided in the next section.
4 The handleHolidayRequest(..) method is the main handling method, which gets passed the <HolidayRequest/> element from the incoming XML message. The @RequestPayload annotation indicates that the holidayRequest parameter should be mapped to the payload of the request message. We use the XPath expressions to extract the string values from the XML messages and convert these values to Date objects by using a SimpleDateFormat (the parseData method). With these values, we invoke a method on the business service. Typically, this results in a database transaction being started and some records being altered in the database. Finally, we define a void return type, which indicates to Spring-WS that we do not want to send a response message. If we want a response message, we could return a JDOM Element to represent the payload of the response message.

Using JDOM is just one of the options to handle the XML. Other options include DOM, dom4j, XOM, SAX, and StAX, but also marshalling techniques like JAXB, Castor, XMLBeans, JiBX, and XStream, as explained in the next chapter. We chose JDOM because it gives us access to the raw XML and because it is based on classes (not interfaces and factory methods as with W3C DOM and dom4j), which makes the code less verbose. We use XPath because it is less fragile than marshalling technologies. We do not need strict schema conformance as long as we can find the dates and the name.spring-doc.cadn.net.cn

Because we use JDOM, we must add some dependencies to the Maven pom.xml, which is in the root of our project directory. Here is the relevant section of the POM:spring-doc.cadn.net.cn

<dependencies>
    <dependency>
        <groupId>org.springframework.ws</groupId>
        <artifactId>spring-ws-core</artifactId>
        <version></version>
    </dependency>
    <dependency>
        <groupId>jdom</groupId>
        <artifactId>jdom</artifactId>
        <version>2.0.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1</version>
    </dependency>
</dependencies>

Here is how we would configure these classes in our spring-ws-servlet.xml Spring XML configuration file by using component scanning. We also instruct Spring-WS to use annotation-driven endpoints, with the <sws:annotation-driven> element.spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:sws="http://www.springframework.org/schema/web-services"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package="com.mycompany.hr"/>

  <sws:annotation-driven/>

</beans>

3.5.2. Routing the Message to the Endpoint

As part of writing the endpoint, we also used the @PayloadRoot annotation to indicate which sort of messages can be handled by the handleHolidayRequest method. In Spring-WS, this process is the responsibility of an EndpointMapping. Here, we route messages based on their content by using a PayloadRootAnnotationMethodEndpointMapping. The following listing shows the annotation we used earlier:spring-doc.cadn.net.cn

@PayloadRoot(namespace = "http://mycompany.com/hr/schemas", localPart = "HolidayRequest")

The annotation shown in the preceding example basically means that whenever an XML message is received with the namespace http://mycompany.com/hr/schemas and the HolidayRequest local name, it is routed to the handleHolidayRequest method. By using the <sws:annotation-driven> element in our configuration, we enable the detection of the @PayloadRoot annotations. It is possible (and quite common) to have multiple, related handling methods in an endpoint, each of them handling different XML messages.spring-doc.cadn.net.cn

There are also other ways to map endpoints to XML messages, which is described in the next chapter.spring-doc.cadn.net.cn

3.5.3. Providing the Service and Stub implementation

Now that we have the endpoint, we need HumanResourceService and its implementation for use by HolidayEndpoint. The following listing shows the HumanResourceService interface:spring-doc.cadn.net.cn

package com.mycompany.hr.service;

import java.util.Date;

public interface HumanResourceService {
    void bookHoliday(Date startDate, Date endDate, String name);
}

For tutorial purposes, we use a simple stub implementation of the HumanResourceService:spring-doc.cadn.net.cn

package com.mycompany.hr.service;

import java.util.Date;

import org.springframework.stereotype.Service;

@Service                                                                 (1)
public class StubHumanResourceService implements HumanResourceService {
    public void bookHoliday(Date startDate, Date endDate, String name) {
        System.out.println("Booking holiday for [" + startDate + "-" + endDate + "] for [" + name + "] ");
    }
}
1 The StubHumanResourceService is annotated with @Service. This marks the class as a business facade, which makes this a candidate for injection by @Autowired in HolidayEndpoint.

3.6. Publishing the WSDL

Finally, we need to publish the WSDL. As stated in Service Contract, we do not need to write a WSDL ourselves. Spring-WS can generate one based on some conventions. Here is how we define the generation:spring-doc.cadn.net.cn

<sws:dynamic-wsdl id="holiday"                                (1)
    portTypeName="HumanResource"                              (3)
    locationUri="/holidayService/"                            (4)
    targetNamespace="http://mycompany.com/hr/definitions">    (5)
  <sws:xsd location="/WEB-INF/hr.xsd"/>                       (2)
</sws:dynamic-wsdl>
1 The id determines the URL where the WSDL can be retrieved. In this case, the id is holiday, which means that the WSDL can be retrieved as holiday.wsdl in the servlet context. The full URL is http://localhost:8080/holidayService/holiday.wsdl.
2 Next, we set the WSDL port type to be HumanResource.
3 We set the location where the service can be reached: /holidayService/. We use a relative URI, and we instruct the framework to transform it dynamically to an absolute URI. Hence, if the service is deployed to different contexts, we do not have to change the URI manually. For more information, see the section called “Automatic WSDL exposure”. For the location transformation to work, we need to add an init parameter to spring-ws servlet in web.xml (shown in the next listing).
4 We define the target namespace for the WSDL definition itself. Setting this attribute is not required. If not set, the WSDL has the same namespace as the XSD schema.
5 The xsd element refers to the human resource schema we defined in Data Contract. We placed the schema in the WEB-INF directory of the application.

The following listing shows how to add the init parameter:spring-doc.cadn.net.cn

<init-param>
  <param-name>transformWsdlLocations</param-name>
  <param-value>true</param-value>
</init-param>

You can create a WAR file by using mvn install. If you deploy the application (to Tomcat, Jetty, and so on) and point your browser at this location, you see the generated WSDL. This WSDL is ready to be used by clients, such as soapUI or other SOAP frameworks.spring-doc.cadn.net.cn

That concludes this tutorial. The tutorial code can be found in the full distribution of Spring-WS. If you wish to continue, look at the echo sample application that is part of the distribution. After that, look at the airline sample, which is a bit more complicated, because it uses JAXB, WS-Security, Hibernate, and a transactional service layer. Finally, you can read the rest of the reference documentation.spring-doc.cadn.net.cn