前言

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

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

I. 引言

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

1. 什么是 Spring Web 服务?

1.1. 简介

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

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

1.1.1. 强大的映射

您可以将传入的 XML 请求分发到任何对象,具体取决于消息负载、SOAP Action 标头或 XPath 表达式。spring-doc.cn

1.1.2. XML API 支持

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

1.1.3. 灵活的 XML 编组

Spring Web 服务构建在 Spring 框架中的 Object/XML 映射模块之上,该模块支持 JAXB 1 和 2、Castor、XMLBeans、JiBX 和 XStream。spring-doc.cn

1.1.4. 重用您的 Spring 专业知识

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

1.1.5. 对 WS-Security 的支持

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

1.1.6. 与 Spring Security 集成

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

1.1.7. Apache 许可证

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

1.2. 运行环境

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

Spring-WS 由许多模块组成,这些模块将在本节的其余部分进行描述。spring-doc.cn

  • XML 模块 () 包含 Spring Web 服务的各种 XML 支持类。该模块主要面向 Spring-WS 框架本身,而不是 Web 服务开发人员。spring-xml.jarspring-doc.cn

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

  • Support 模块 () 包含其他传输方式(JMS、Email 等)。spring-ws-support.jarspring-doc.cn

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

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

Spring DEPS

1.3. 支持的标准

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

  • SOAP 1.1 和 1.2spring-doc.cn

  • WSDL 1.1 和 2.0(仅 WSDL 1.1 支持基于 XSD 的生成)spring-doc.cn

  • WS-I 基本配置文件 1.0、1.1、1.2 和 2.0spring-doc.cn

  • WS-Addressing 1.0 和 2004 年 8 月草案spring-doc.cn

  • SOAP 消息安全 1.1、用户名令牌配置文件 1.1、X.509 证书令牌配置文件 1.1、SAML 令牌配置文件 1.1、Kerberos 令牌配置文件 1.1、基本安全配置文件 1.1spring-doc.cn

2. 为什么先签订合同?

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

什么是 WSDL?spring-doc.cn

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

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

2.1. Object/XML 阻抗不匹配

与 ORM 领域类似,我们遇到了 Object/Relational 阻抗不匹配的情况,将 Java 对象转换为 XML 也有类似的问题。乍一看,O/X 映射问题似乎很简单:为每个 Java 对象创建一个 XML 元素,以将所有 Java 属性和字段转换为子元素或属性。但是,事情并不像看起来那么简单,因为分层语言(如 XML,尤其是 XSD)和 Java 的图形模型之间存在根本差异。spring-doc.cn

本节中的大部分内容都受到了 [alpine][effective-enterprise-java] 的启发。

2.1.1. XSD 扩展

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

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

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

2.1.2. 不可移植类型

Web 服务最重要的目标之一是可互操作:支持多个平台,如 Java、.NET、Python 等。由于所有这些语言都有不同的类库,因此必须使用某种通用的跨语言格式在它们之间进行通信。该格式是 XML,所有这些语言都支持这种格式。spring-doc.cn

由于这种转换,您必须确保在服务实现中使用可移植类型。例如,考虑一个返回 :java.util.TreeMapspring-doc.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,许多平台也没有类似于 .因此,当 .NET 客户端访问您的 Web 服务时,它可能以具有不同语义的 . 结尾。TreeMapSystem.Collections.Hashtablespring-doc.cn

在客户端工作时,也存在此问题。请考虑以下描述服务协定的 XSD 代码段:spring-doc.cn

<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 调用此服务,我们可能会使用 a 或 .但是,这两个类实际上都描述时间,而不是日期。因此,我们实际上最终会发送表示 2007 年 4 月 4 日午夜 () 的数据,这与 .datejava.util.Datejava.util.Calendar2007-04-04T00:00:002007-04-04spring-doc.cn

2.1.3. 循环图

假设我们有以下类结构:spring-doc.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
}

这是一个循环图:the 引用 ,而 则再次引用 。像这样的循环图在 Java 中很常见。如果我们采取一种天真的方法将其转换为 XML,我们最终会得到如下结果:FlightPassengerFlightspring-doc.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.cn

解决此问题的一种方法是使用对已编组对象的引用:spring-doc.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.cn

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

2.2. 合约优先与合约后

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

2.2.1. 脆弱性

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

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

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

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

2.2.2. 性能

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

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

2.2.3. 可重用性

通过在单独的文件中定义架构,您可以在不同场景中重复使用该文件。考虑名为 的文件中 an 的定义:AirportCodeairline.xsdspring-doc.cn

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

通过使用语句,您可以在其他架构甚至 WSDL 文件中重用此定义。importspring-doc.cn

2.2.4. 版本控制

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

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

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

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

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

在本教程中,我们将定义一个由 Human Resources 部门创建的 Web 服务。客户可以向该服务发送假期申请表以预订假期。spring-doc.cn

3.1. 消息

在本节中,我们将重点介绍发送到 Web 服务和从 Web 服务发送的实际 XML 消息。我们首先确定这些消息是什么样子的。spring-doc.cn

3.1.1. 假期

在这个场景中,我们必须处理假期请求,因此确定 XML 中的假期是有意义的:spring-doc.cn

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

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

3.1.2. 员工

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

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

我们使用了与以前相同的命名空间。如果此元素可用于其他方案,则使用不同的命名空间(如 .<Employee/>http://example.com/employees/schemasspring-doc.cn

3.1.3. HolidayRequest

元素和元素都可以放在 :holidayemployee<HolidayRequest/>spring-doc.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.cn

3.2. 数据合约

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

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

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

使用前面描述的示例,我们最终会得到以下生成的架构:spring-doc.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 服务应该能够接受所有这些元素作为数据。这是不可取的:我们只想接受 .通过删除 wrapping element 标签(从而保留类型)并内联结果,我们可以完成此操作,如下所示:<HolidayRequest/>spring-doc.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.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 有一个很好的内置类型,我们可以使用。我们还将 s 更改为 instances。最后,我们将 in 更改为 。这告诉 XML 解析器,和 的顺序并不重要。我们最终的 XSD 现在如下面的清单所示:dateNCNamestringsequence<HolidayRequest/>all<Holiday/><Employee/>spring-doc.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.xsdspring-doc.cn

3.3. 服务合同

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

我们从标准序言开始 WSDL,并导入现有的 XSD。为了将架构与定义分开,我们为 WSDL 定义使用单独的命名空间:.以下清单显示了 preamble:http://mycompany.com/hr/definitionsspring-doc.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>

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

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

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

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

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

添加混凝土部分是非常标准的。为此,请参阅您之前定义的抽象部分,确保用于元素( 已弃用),为操作选择 a(在本例中为 ,但任何 URI 都有效),并确定您希望请求到达的 URL(在本例中为 ):document/literalsoap:bindingrpc/encodedsoapActionhttp://mycompany.com/RequestHolidaylocationhttp://mycompany.com/humanresourcesspring-doc.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 我们定义消息,该消息在 .HolidayRequestportType
3 类型在 schema 中定义。HolidayRequest
4 我们定义端口类型,该类型在 .HumanResourcebinding
5 我们定义绑定,它在 .HumanResourceBindingport
6 我们使用 document/literal 样式。
7 文本表示 HTTP 传输。http://schemas.xmlsoap.org/soap/http
8 该属性表示将随每个请求一起发送的 HTTP 标头。soapActionSOAPAction
9 地址是可以调用 Web 服务的 URL。http://localhost:8080/holidayService/

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

3.4. 创建项目

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

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

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

上述命令将创建一个名为 .此目录中是一个目录,其中包含 WAR 文件的根目录。您可以在此处找到标准的 Web 应用程序部署描述符 (),它定义了一个 Spring-WS 并将所有传入的请求映射到此 servlet:holidayServicesrc/main/webapp'WEB-INF/web.xml'MessageDispatcherServletspring-doc.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>

除了前面的文件之外,您还需要另一个特定于 Spring-WS 的配置文件,名为 .此文件包含所有特定于 Spring-WS 的 bean,例如 and 和 ,用于创建新的 Spring 容器。此文件的名称派生自附带的 servlet(在本例中为 )的名称,并附加了它。因此,如果定义 name 为 ,则特定于 Spring-WS 的配置文件的名称将变为 。WEB-INF/web.xmlWEB-INF/spring-ws-servlet.xmlEndPointsWebServiceMessageReceivers'spring-ws'-servlet.xmlMessageDispatcherServlet'dynamite'WEB-INF/dynamite-servlet.xmlspring-doc.cn

(您可以在 [tutorial.example.sws-conf-file] 中查看此示例的文件内容。WEB-INF/spring-ws-servlet.xmlspring-doc.cn

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

3.5. 实现端点

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

3.5.1. 处理 XML 消息

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

下面的清单显示了定义我们的 holiday 端点的类:spring-doc.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 用 .这将该类标记为一种特殊类型的 ,适合在 Spring-WS 中处理 XML 消息,并且还使其有资格进行 适合组件扫描。HolidayEndpoint@Endpoint@Component
2 这需要业务服务来运行,因此我们在构造函数中注入依赖项,并用 . 接下来,我们使用 JDOM2 API 设置 XPath 表达式。有四个表达式:用于提取文本值、用于提取结束日期和两个用于提取员工姓名的表达式。HolidayEndpointHumanResourceService@Autowired//hr:StartDate<StartDate>//hr:EndDate
3 该注释告诉 Spring-WS 该方法适合处理 XML 消息。此方法可以处理的消息类型由 annotation 值指示。在这种情况下,它可以 handle 具有 local 部分和 namespace 的 XML 元素。 有关将消息映射到终端节点的更多信息,请参阅下一节。@PayloadRoothandleHolidayRequestHolidayRequesthttp://mycompany.com/hr/schemas
4 该方法是主要的处理方法,它从传入的 XML 消息中传递元素。该注释指示该参数应映射到 request 消息。我们使用 XPath 表达式从 XML 消息中提取字符串值,并使用 a(方法)将这些值转换为对象。使用这些值,我们在业务服务上调用一个方法。 通常,这会导致启动数据库事务并更改数据库中的某些记录。 最后,我们定义一个返回类型,它向 Spring-WS 表明我们不想发送响应消息。 如果我们想要响应消息,我们可以返回一个 JDOM 元素来表示响应消息的有效负载。handleHolidayRequest(..)<HolidayRequest/>@RequestPayloadholidayRequestDateSimpleDateFormatparseDatavoid

使用 JDOM 只是处理 XML 的选项之一。其他选项包括 DOM、dom4j、XOM、SAX 和 StAX,但也包括 JAXB、Castor、XMLBeans、JiBX 和 XStream 等编组技术,如下章所述。我们选择 JDOM 是因为它允许我们访问原始 XML,并且因为它基于类(而不是像 W3C DOM 和 dom4j 那样基于接口和工厂方法),这使得代码不那么冗长。我们使用 XPath 是因为它比编组技术更不脆弱。我们不需要严格的 schema 一致性,只要我们能找到日期和名称就行了。spring-doc.cn

因为我们使用 JDOM,所以我们必须向 Maven 添加一些依赖项 ,它位于我们项目目录的根目录中。以下是 POM 的相关部分:pom.xmlspring-doc.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>

以下是我们如何使用组件扫描在 Spring XML 配置文件中配置这些类。我们还指示 Spring-WS 使用 Comments 驱动的端点,以及 element.spring-ws-servlet.xml<sws:annotation-driven>spring-doc.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. 将消息路由到端点

作为编写端点的一部分,我们还使用了 annotation 来指示该方法可以处理哪些类型的消息。在 Spring-WS 中,此过程由 .在这里,我们使用 .下面的清单显示了我们之前使用的 Comments:@PayloadRoothandleHolidayRequestEndpointMappingPayloadRootAnnotationMethodEndpointMappingspring-doc.cn

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

前面示例中显示的注释基本上意味着,每当收到带有命名空间和本地名称的 XML 消息时,它都会路由到该方法。通过在配置中使用该元素,我们可以启用注释检测。一个端点中可能(并且很常见)有多个相关的处理方法,每个方法处理不同的 XML 消息。http://mycompany.com/hr/schemasHolidayRequesthandleHolidayRequest<sws:annotation-driven>@PayloadRootspring-doc.cn

还有其他方法可以将端点映射到 XML 消息,这将在下一章中介绍。spring-doc.cn

3.5.3. 提供服务和 Stub 实现

现在我们有了终端节点,我们需要及其实现以供 .下面的清单显示了该接口:HumanResourceServiceHolidayEndpointHumanResourceServicespring-doc.cn

package com.mycompany.hr.service;

import java.util.Date;

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

出于教程目的,我们使用 :HumanResourceServicespring-doc.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 用 .这会将类标记为业务外观,这使得该类成为 in 的候选 INJECTION 对象。StubHumanResourceService@Service@AutowiredHolidayEndpoint

3.6. 发布 WSDL

最后,我们需要发布 WSDL。正如 服务契约 中所述,我们不需要自己编写 WSDL。Spring-WS 可以根据一些约定生成一个。以下是我们如何定义代:spring-doc.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 确定可以检索 WSDL 的 URL。在这种情况下,is 表示可以检索 WSDL 就像在 Servlet 上下文中一样。完整的 URL 是 .ididholidayholiday.wsdlhttp://localhost:8080/holidayService/holiday.wsdl
2 接下来,我们将 WSDL 端口类型设置为 .HumanResource
3 我们设置可以访问服务的位置:。我们使用相对 URI,并指示框架对其进行转换 动态转换为绝对 URI。因此,如果服务部署到不同的上下文,我们不必手动更改 URI。 有关更多信息,请参阅名为“自动 WSDL 公开”的部分。要使位置转换正常工作,我们需要向 servlet 中添加一个 init 参数(如下一个清单所示)。/holidayService/spring-wsweb.xml
4 我们为 WSDL 定义本身定义目标名称空间。不需要设置此属性。如果未设置,则 WSDL 与 XSD 架构具有相同的名称空间。
5 该元素引用了我们在 Data Contract 中定义的 Human Resource Schema。我们将 schema 放在应用程序的目录中。xsdWEB-INF

下面的清单显示了如何添加 init 参数:spring-doc.cn

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

您可以使用 创建 WAR 文件。如果部署应用程序(部署到 Tomcat、Jetty 等)并将浏览器指向此位置,则会看到生成的 WSDL。此 WSDL 可供客户端(如 soapUI 或其他 SOAP 框架)使用。mvn installspring-doc.cn

本教程到此结束。教程代码可以在 Spring-WS 的完整发行版中找到。如果您想继续,请查看作为发行版一部分的 echo 示例应用程序。之后,看一下 airline 示例,它稍微复杂一些,因为它使用了 JAXB、WS-Security、Hibernate 和事务服务层。最后,您可以阅读参考文档的其余部分。spring-doc.cn