© 2005-2020 原作者。

本文档的副本可以供您自己使用和分发给其他人,前提是您不对此类副本收取任何费用,并且进一步前提是每份副本都包含本版权声明,无论是印刷版还是电子版。

前言

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

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

1.简介

参考文档的第一部分是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-WS 的原因有很多,但大多数人在发现在遵循 Web 服务最佳实践方面缺乏替代 SOAP 堆栈后被它所吸引。Spring-WS 使最佳实践成为一种简单的实践。这包括诸如 WS-I 基本概要、契约优先开发以及契约和实现之间的松散耦合等实践。Spring Web Services 的其他关键特性是:

1.1.1。强大的映射

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

1.1.2。XML API 支持

传入的 XML 消息不仅可以使用 DOM、SAX 和 StAX 等标准 JAXP API 处理,还可以使用 JDOM、dom4j、XOM 甚至编组技术处理。

1.1.3。灵活的 XML 编组

Spring Web Services 基于 Spring Framework 中的 Object/XML Mapping 模块构建,该模块支持 JAXB 1 和 2、Castor、XMLBeans、JiBX 和 XStream。

1.1.4。重用您的 Spring 专业知识

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

1.1.5。支持 WS-Security

WS-Security 允许您对 SOAP 消息进行签名、加密和解密它们,或者针​​对它们进行身份验证。

1.1.6。与 Spring Security 集成

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

1.1.7。阿帕奇许可证

您可以REST Assured地在项目中使用 Spring-WS。

1.2. 运行环境

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

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

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

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

  • 支持模块 ( spring-ws-support.jar) 包含其他传输(JMS、电子邮件等)。

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

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

弹簧部门

1.3. 支持的标准

Spring Web Services 支持以下标准:

  • SOAP 1.1 和 1.2

  • WSDL 1.1 和 2.0(仅 WSDL 1.1 支持基于 XSD 的生成)

  • WS-I 基本概要 1.0、1.1、1.2 和 2.0

  • WS-Addressing 1.0 和 2004 年 8 月的草案

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

2. 为什么要先签约?

在创建 Web 服务时,有两种开发风格:契约后和契约优先。当您使用最后契约的方法时,您从 Java 代码开始,并从中生成 Web 服务契约(在 WSDL 中 — 见边栏)。使用契约优先时,您从 WSDL 契约开始,并使用 Java 来实现契约。

什么是 WSDL?

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

Spring-WS 仅支持契约优先的开发风格,本节将解释原因。

2.1。对象/XML 阻抗不匹配

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

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

2.1.1。XSD 扩展

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

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

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

2.1.2. 不可移植的类型

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

由于这种转换,您必须确保在服务实现中使用可移植类型。例如,考虑一个返回 a 的服务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具有不同语义的 .NET 结尾。

在客户端工作时也会出现此问题。考虑下面的 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 调用此服务,我们可能会使用 ajava.util.Datejava.util.Calendar. 然而,这两个类实际上描述的是时间,而不是日期。因此,我们实际上最终发送了代表 2007 年 4 月 4 日午夜 ( 2007-04-04T00:00:00) 的数据,这与2007-04-04.

2.1.3。循环图

假设我们有以下类结构:

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
}

这是一个循环图: theFlight指的是 the Passenger,它指的是 the Flightagain。像这样的循环图在 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/编码)中使用这些引用的标准方式已被弃用,取而代之的是 document/literal(请参阅 WS-I Basic Profile)。

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

2.2. 合同优先与合同最后

除了上一节提到的对象/XML 映射问题之外,还有其他原因更喜欢契约优先的开发风格。

2.2.1。脆弱性

如前所述,契约后开发风格导致您的 Web 服务契约(WSDL 和 XSD)是从 Java 契约(通常是一个接口)生成的。如果您使用这种方法,您无法保证合同随着时间的推移保持不变。每次更改 Java 合约并重新部署它时,Web 服务合约都可能发生后续更改。

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

当 Web 服务合同发生更改时,必须指示合同的用户获取新合同并可能更改他们的代码以适应合同中的任何更改。

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

2.2.2。表现

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

使用契约优先时,您明确地描述了将什么 XML 发送到哪里,从而确保它正是您想要的。

2.2.3。可重用性

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

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

import您可以通过使用语句在其他模式甚至 WSDL 文件中重用此定义。

2.2.4。版本控制

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

如果使用契约优先,我们可以在契约和实现之间有一个更松散的耦合。这种更松散的耦合让我们可以在一个类中实现合约的两个版本。例如,我们可以使用 XSLT 样式表将任何“旧式”消息转换为“新式”消息。

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

本教程向您展示如何编写契约优先的 Web 服务 ,即如何开发首先以 XML Schema 或 WSDL 契约开始,然后是 Java 代码的 Web 服务。Spring-WS 专注于这种开发风格,本教程应该可以帮助您入门。请注意,本教程的第一部分几乎不包含 Spring-WS 特定信息。它主要是关于 XML、XSD 和 WSDL。第二部分侧重于使用 Spring-WS 实现这个契约。

在进行契约优先的 Web 服务开发时,最重要的事情是考虑 XML。这意味着 Java 语言概念的重要性较低。它是通过网络发送的 XML,您应该关注它。用于实现 Web 服务的 Java 是一个实现细节。

在本教程中,我们定义了一个由人力资源部门创建的 Web 服务。客户可以向此服务发送假期申请表来预订假期。

3.1。留言

在本节中,我们将重点关注发送到 Web 服务和从 Web 服务发送的实际 XML 消息。我们首先确定这些消息的外观。

3.1.1。假期

在场景中,我们必须处理假期请求,因此确定假期在 XML 中的样子是有意义的:

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

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

3.1.2。员工

场景中还有员工的概念。这是它在 XML 中的样子:

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

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

3.1.3. 假期申请

holidayelement 和element都employee可以放在 a 中<HolidayRequest/>

<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/>可能是第一个元素。重要的是所有数据都在那里。事实上,数据是唯一重要的东西:我们采用数据驱动的方法。

3.2. 数据合约

现在我们已经看到了一些我们可以使用的 XML 数据示例,将其形式化为模式是有意义的。该数据合同定义了我们接受的消息格式。有四种不同的方式可以为 XML 定义这样的契约:

DTD 对命名空间的支持有限,因此它们不适用于 Web 服务。Relax NG 和 Schematron 比 XML Schema 更容易。不幸的是,它们并没有得到跨平台的广泛支持。因此,我们使用 XML Schema。

到目前为止,创建 XSD 的最简单方法是从示例文档中推断出它。任何优秀的 XML 编辑器或 Java IDE 都提供此功能。基本上,这些工具使用一些示例 XML 文档来生成验证它们的模式。最终结果当然需要完善,但这是一个很好的起点。

使用前面描述的示例,我们最终得到以下生成的模式:

<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/>. 通过删除包装元素标签(从而保留类型)并内联结果,我们可以做到这一点,如下所示:

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

该模式仍然存在一个问题:使用这样的模式,您可以期望以下消息得到验证:

<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 有一个我们可以使用的优秀的内置date类型。我们还将NCNames 更改为string实例。最后,我们将sequencein更改<HolidayRequest/>all. 这告诉 XML 解析器 和 的顺序<Holiday/>不重要<Employee/>。我们最终的 XSD 现在看起来像下面的清单:

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

3.3. 服务合同

服务契约通常表示为WSDL文件。请注意,在 Spring-WS 中,不需要手动编写 WSDL。基于 XSD 和一些约定,Spring-WS 可以为您创建 WSDL,如“实现端点”部分所述。本节的其余部分展示了如何手动编写 WSDL。您可能想跳到下一节

我们从标准序言开始我们的 WSDL,并通过导入我们现有的 XSD。为了将模式与定义分开,我们为 WSDL 定义使用单独的命名空间:http://mycompany.com/hr/definitions. 以下清单显示了序言:

<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/>我们将其放入架构中:

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

我们将消息添加到端口类型作为操作:

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

该消息完成了 WSDL 的抽象部分(可以说是接口)并留下具体部分。具体部分由a binding(告诉客户端如何调用您刚刚定义的操作)和a service(告诉客户端在哪里调用它)组成。

添加一个具体的部分是非常标准的。为此,请参考您之前定义的抽象部分,确保您使用document/literal元素soap:bindingrpc/encoded不推荐使用),soapAction为操作选择一个(在这种情况下http://mycompany.com/RequestHoliday,,但任何 URI 都可以),并确定location您想要的 URL请求到达(在这种情况下,http://mycompany.com/humanresources):

<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中定义的模式。
2 我们定义了HolidayRequest消息,它在portType.
3 HolidayRequest类型在模式中定义。
4 我们定义HumanResource端口类型,在binding.
5 我们定义了HumanResourceBinding绑定,它在port.
6 我们使用文档/文字样式。
7 字面http://schemas.xmlsoap.org/soap/http量表示 HTTP 传输。
8 soapAction属性表示SOAPAction将随每个请求发送的 HTTP 标头。
9 http://localhost:8080/holidayService/地址是可以调用 Web 服务的 URL 。

前面的清单显示了最终的 WSDL。我们将在下一节中描述如何实现生成的模式和 WSDL。

3.4. 创建项目

在本节中,我们使用Maven为我们创建初始项目结构。这样做不是必需的,但可以大大减少我们为设置 HolidayService 而编写的代码量。

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

mvn 原型:创建 -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:

<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使用 name定义 a 'dynamite',则 Spring-WS 特定配置文件的名称将变为WEB-INF/dynamite-servlet.xml.

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

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

3.5. 实现端点

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

3.5.1。处理 XML 消息

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

以下清单显示了定义我们的假期端点的类:

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 HolidayEndpoint注释@Endpoint。这标志着该类是一种特殊的类@Component,适合在 Spring-WS 中处理 XML 消息,也使其适合于组件扫描。
2 HolidayEndpoint需要业务服务才能运行,所以我们在HumanResourceService构造函数中注入依赖并用@Autowired. 接下来,我们使用 JDOM2 API 设置 XPath 表达式。有四种表达式://hr:StartDate用于提取<StartDate>文本值,//hr:EndDate用于提取结束日期,以及用于提取员工姓名的两种。
3 @PayloadRoot注解告诉 Spring-WS 该方法handleHolidayRequest适用于处理 XML 消息。此方法可以处理的消息类型由注释值指示。在这种情况下,它可以处理具有HolidayRequest本地部分和http://mycompany.com/hr/schemas名称空间的 XML 元素。下一节将提供有关将消息映射到端点的更多信息。
4 handleHolidayRequest(..)方法是主要的处理方法,它<HolidayRequest/> 从传入的 XML 消息中获取元素。@RequestPayload注解表示该参数holidayRequest应该映射到请求消息的有效负载。我们使用 XPath 表达式从 XML 消息中提取字符串值,并Date使用 a SimpleDateFormatparseData方法)将这些值转换为对象。使用这些值,我们调用业务服务上的方法。通常,这会导致启动数据库事务并更改数据库中的某些记录。最后,我们定义一个void返回类型,向 Spring-WS 指示我们不想发送响应消息。如果我们想要响应消息,我们可以返回一个 JDOM 元素来表示响应消息的负载。

使用 JDOM 只是处理 XML 的选项之一。其他选项包括 DOM、dom4j、XOM、SAX 和 StAX,还包括编组技术,如 JAXB、Castor、XMLBeans、JiBX 和 XStream,下一章将对此进行说明。我们选择 JDOM 是因为它使我们能够访问原始 XML,并且因为它基于类(而不是 W3C DOM 和 dom4j 那样的接口和工厂方法),这使得代码不那么冗长。我们使用 XPath 是因为它没有编组技术那么脆弱。只要我们能找到日期和名称,我们就不需要严格的模式一致性。

因为我们使用 JDOM,所以我们必须pom.xml在项目目录的根目录下的 Maven 中添加一些依赖项。这是POM的相关部分:

<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-ws-servlet.xml使用组件扫描在 Spring XML 配置文件中配置这些类。我们还指示 Spring-WS 将注释驱动的端点与<sws:annotation-driven>元素一起使用。

<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. 将消息路由到端点

作为编写端点的一部分,我们还使用@PayloadRoot注释来指示该方法可以处理哪种类型的消息handleHolidayRequest。在 Spring-WS 中,这个过程是由EndpointMapping. 在这里,我们使用PayloadRootAnnotationMethodEndpointMapping. 以下清单显示了我们之前使用的注释:

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

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

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

3.5.3. 提供服务和存根实现

现在我们有了端点,我们需要HumanResourceService它的实现以供HolidayEndpoint. 以下清单显示了该HumanResourceService界面:

package com.mycompany.hr.service;

import java.util.Date;

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

出于教程目的,我们使用一个简单的存根实现HumanResourceService

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 StubHumanResourceService注释@Service@Autowired这将类标记为业务门面,这使得它成为in注入的候选对象HolidayEndpoint

3.6. 发布 WSDL

最后,我们需要发布 WSDL。如Service Contract中所述,我们不需要自己编写 WSDL。Spring-WS 可以根据一些约定生成一个。以下是我们如何定义世代:

<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 id确定可以检索 WSDL 的 URL 。在这种情况下,idis holiday,这意味着可以holiday.wsdl在 servlet 上下文中检索 WSDL。完整的 URL 是http://localhost:8080/holidayService/holiday.wsdl
2 接下来,我们将 WSDL 端口类型设置为HumanResource.
3 我们设置服务可以到达的位置:/holidayService/. 我们使用相对 URI,并指示框架将其动态转换为绝对 URI。因此,如果服务部署到不同的上下文,我们不必手动更改 URI。有关更多信息,请参阅名为“自动 WSDL 公开”的部分。为了使位置转换起作用,我们需要在spring-ws servlet 中添加一个 init 参数web.xml(如下面的清单所示)。
4 我们为 WSDL 定义本身定义目标名称空间。不需要设置此属性。如果未设置,则 WSDL 具有与 XSD 模式相同的名称空间。
5 xsd元素指的是我们在Data Contract中定义的人力资源模式。我们将模式放在WEB-INF应用程序的目录中。

以下清单显示了如何添加 init 参数:

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

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

本教程到此结束。教程代码可以在 Spring-WS 的完整发行版中找到。如果您希望继续,请查看作为分发的一部分的 echo 示例应用程序。之后,看一下航空公司示例,它有点复杂,因为它使用 JAXB、WS-Security、Hibernate 和事务服务层。最后,您可以阅读其余的参考文档。

2.参考

这部分参考文档详细介绍了构成 Spring Web 服务的各种组件。这包括一章讨论客户端和服务器端 WS 共有的部分,一章专门讨论编写服务器端 Web 服务的细节,一章关于在客户端使用 Web 服务,以及一章关于使用WS-安全

4. 共享组件

本章探讨在客户端和服务器端 Spring-WS 开发之间共享的组件。这些接口和类代表了 Spring-WS 的构建块,因此您需要了解它们的作用,即使您不直接使用它们。

4.1。Web 服务消息

本节介绍 Spring-WS 使用的消息和消息工厂。

4.1.1。WebServiceMessage

Spring Web Services 的核心接口之一是WebServiceMessage. 此接口表示与协议无关的 XML 消息。javax.xml.transform.Source该接口包含以 a或 a的形式提供对消息有效负载的访问的方法javax.xml.transform.ResultSource并且Result是标记接口,表示对 XML 输入和输出的抽象。具体实现包装了各种 XML 表示,如下表所示:

源或结果实现 包装的 XML 表示

javax.xml.transform.dom.DOMSource

org.w3c.dom.Node

javax.xml.transform.dom.DOMResult

org.w3c.dom.Node

javax.xml.transform.sax.SAXSource

org.xml.sax.InputSourceorg.xml.sax.XMLReader

javax.xml.transform.sax.SAXResult

org.xml.sax.ContentHandler

javax.xml.transform.stream.StreamSource

java.io.File, java.io.InputStream, 或java.io.Reader

javax.xml.transform.stream.StreamResult

java.io.File, java.io.OutputStream, 或java.io.Writer

除了读取和写入有效负载之外,Web 服务消息还可以将自身写入输出流。

4.1.2.SoapMessage

SoapMessage是 的子类WebServiceMessage。它包含特定于 SOAP 的方法,例如获取 SOAP 标头、SOAP 错误等。一般来说,你的代码不应该依赖于,因为 SOAP Body 的内容(消息的SoapMessage负载)可以通过在. 只有在需要执行特定于 SOAP 的操作(例如添加标头、获取附件等)时,您才需要强制转换为.getPayloadSource()getPayloadResult()WebServiceMessageWebServiceMessageSoapMessage

4.1.3。消息工厂

具体的消息实现由WebServiceMessageFactory. 该工厂可以创建空消息或从输入流中读取消息。有两种具体的实现WebServiceMessageFactory。一种是基于 SAAJ,即用于 Java 的带有附件 API 的 SOAP。另一种是基于 Axis 2 的 AXIOM(AXis 对象模型)。

SaajSoapMessageFactory

使用SaajSoapMessageFactorySOAP with Attachments API for Java (SAAJ) 创建SoapMessage实现。SAAJ 是 J2EE 1.4 的一部分,因此大多数现代应用程序服务器都应该支持它。以下是常见应用程序服务器提供的 SAAJ 版本的概述:

应用服务器 SAAJ版

东亚网络逻辑 8

1.1

东亚网络逻辑 9

1.1/1.2 1

IBM WebSphere 6

1.2

太阳玻璃鱼 1

1.3

1 Weblogic 9 在 SAAJ 1.2 实现中存在一个已知错误:它实现了所有 1.2 接口,但UnsupportedOperationException在调用时抛出一个。Spring Web Services 有一个解决方法:它在 WebLogic 9 上运行时使用 SAAJ 1.1。

此外,Java SE 6 包括 SAAJ 1.3。您可以SaajSoapMessageFactory按如下方式连接:

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
SAAJ 基于 DOM,即文档对象模型。这意味着所有 SOAP 消息都存储在内存中。对于较大的 SOAP 消息,这可能无法执行。在这种情况下,AxiomSoapMessageFactory可能更适用。
AxiomSoapMessageFactory

使用AxiomSoapMessageFactoryAXis 2 对象模型 (AXIOM) 创建SoapMessage实现。AXIOM 基于 StAX,即 XML 流 API。StAX 提供了一种基于拉取的机制来读取 XML 消息,这对于较大的消息可能更有效。

要提高 的读取性能AxiomSoapMessageFactory,您可以将该payloadCaching属性设置为 false(默认为 true)。这样做会导致直接从套接字流中读取 SOAP 主体的内容。启用此设置后,有效负载只能读取一次。这意味着您必须确保消息的任何预处理(日志记录或其他工作)都不会消耗它。

您可以使用AxiomSoapMessageFactory如下:

<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory">
    <property name="payloadCaching" value="true"/>
</bean>

除了有效负载缓存之外,AXIOM 还支持完整的流式消息,如StreamingWebServiceMessage. 这意味着您可以直接在响应消息上设置有效负载,而不是将其写入 DOM 树或缓冲区。

当处理程序方法返回 JAXB2 支持的对象时,将使用 AXIOM 的完整流。它会自动将此编组的对象设置到响应消息中,并在响应发出时将其写入传出套接字流。

有关完整流的更多信息,请参阅 和 的类级StreamingWebServiceMessageJavadoc StreamingPayload

SOAP 1.1 或 1.2

theSaajSoapMessageFactory和 theAxiomSoapMessageFactory都有一个soapVersion属性,您可以在其中注入一个SoapVersion常量。默认情况下,版本为 1.1,但您可以将其设置为 1.2:

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

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory">
        <property name="soapVersion">
            <util:constant static-field="org.springframework.ws.soap.SoapVersion.SOAP_12"/>
        </property>
    </bean>

</beans>

在前面的示例中,我们定义了SaajSoapMessageFactory只接受 SOAP 1.2 消息的 a。

尽管 SOAP 的两个版本在格式上非常相似,但 1.2 版本并不向后兼容 1.1,因为它使用不同的 XML 名称空间。SOAPActionSOAP 1.1 和 1.2 之间的其他主要区别包括错误的不同结构以及HTTP 标头已被有效弃用这一事实,尽管它们仍然有效。

SOAP 版本号(或一般的 WS-* 规范版本号)需要注意的一件重要事情是,最新版本的规范通常不是最流行的版本。对于 SOAP,这意味着(当前)最好使用的版本是 1.1。1.2 版将来可能会变得更流行,但 1.1 版目前是最安全的选择。

4.1.4。MessageContext

通常,消息成对出现:请求和响应。在客户端创建一个请求,该请求通过某种传输方式发送到服务器端,并在服务器端生成响应。这个响应被发送回客户端,在那里被读取。

在 Spring Web Services 中,这样的对话包含在 a 中MessageContext,它具有获取请求和响应消息的属性。在客户端,消息上下文由WebServiceTemplate. 在服务器端,从特定于传输的输入流中读取消息上下文。例如,在 HTTP 中,它是从 读取的HttpServletRequest,并且响应被写回到HttpServletResponse.

4.2.TransportContext

SOAP 协议的关键属性之一是它试图与传输无关。这就是为什么,例如,Spring-WS 不支持通过 HTTP 请求 URL 而是通过消息内容将消息映射到端点。

但是,有时需要在客户端或服务器端访问底层传输。为此,Spring Web 服务具有TransportContext. 传输上下文允许访问底层的WebServiceConnection,通常是HttpServletConnection服务器端的 aHttpUrlConnectionCommonsHttpConnection客户端的 a 。例如,您可以在服务器端端点或拦截器中获取当前请求的 IP 地址:

TransportContext context = TransportContextHolder.getTransportContext();
HttpServletConnection connection = (HttpServletConnection )context.getConnection();
HttpServletRequest request = connection.getHttpServletRequest();
String ipAddress = request.getRemoteAddr();

4.3. 使用 XPath 处理 XML

处理 XML 的最佳方法之一是使用 XPath。引用[effective-xml],第 35 项:

XPath 是第四代声明性语言,它允许您指定要处理的节点,而无需准确指定处理器应该如何导航到这些节点。XPath 的数据模型设计得非常好,可以准确地支持几乎所有开发人员希望从 XML 中得到的东西。例如,它合并所有相邻文本,包括 CDATA 部分中的文本,允许计算跳过注释和处理指令的值,并包括来自子元素和后代元素的文本,并要求解析所有外部实体引用。在实践中,XPath 表达式往往对输入文档中的意外但可能微不足道的更改更加健壮。
——艾略特·鲁斯蒂·哈罗德

Spring Web Services 有两种在应用程序中使用 XPath 的方法:更快XPathExpression或更灵活XPathTemplate

4.3.1。XPathExpression

XPathExpression是对已编译 XPath 表达式(例如 Java 5javax.xml.xpath.XPathExpression接口或 JaxenXPath类)的抽象。要在应用程序上下文中构造表达式,您可以使用XPathExpressionFactoryBean. 以下示例使用此工厂 bean:

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

    <bean id="nameExpression" class="org.springframework.xml.xpath.XPathExpressionFactoryBean">
        <property name="expression" value="/Contacts/Contact/Name"/>
    </bean>

    <bean id="myEndpoint" class="sample.MyXPathClass">
        <constructor-arg ref="nameExpression"/>
    </bean>

</beans>

前面的表达式没有使用命名空间,但我们可以使用namespaces工厂 bean 的属性来设置它们。该表达式可以在代码中使用如下:

package sample;

public class MyXPathClass {

    private final XPathExpression nameExpression;

    public MyXPathClass(XPathExpression nameExpression) {
        this.nameExpression = nameExpression;
    }

    public void doXPath(Document document) {
        String name = nameExpression.evaluateAsString(document.getDocumentElement());
        System.out.println("Name: " + name);
    }

}

对于更灵活的方法,您可以使用NodeMapper类似于RowMapperSpring 的 JDBC 支持中的 。以下示例显示了如何使用它:

package sample;

public class MyXPathClass  {

   private final XPathExpression contactExpression;

   public MyXPathClass(XPathExpression contactExpression) {
      this.contactExpression = contactExpression;
   }

   public void doXPath(Document document) {
      List contacts = contactExpression.evaluate(document,
        new NodeMapper() {
           public Object mapNode(Node node, int nodeNum) throws DOMException {
              Element contactElement = (Element) node;
              Element nameElement = (Element) contactElement.getElementsByTagName("Name").item(0);
              Element phoneElement = (Element) contactElement.getElementsByTagName("Phone").item(0);
              return new Contact(nameElement.getTextContent(), phoneElement.getTextContent());
           }
        });
      PlainText Section qName; // do something with the list of Contact objects
   }
}

与 Spring JDBC 中的映射行类似RowMapper,每个结果节点都使用匿名内部类进行映射。在这种情况下,我们创建了一个Contact对象,我们稍后会使用它。

4.3.2.XPathTemplate

XPathExpression允许您仅评估单个预编译的表达式。一个更灵活但速度较慢的替代方案是XpathTemplate. 此类遵循 Spring 中使用的通用模板模式(JdbcTemplateJmsTemplate等)。以下清单显示了一个示例:

package sample;

public class MyXPathClass {

    private XPathOperations template = new Jaxp13XPathTemplate();

    public void doXPath(Source source) {
        String name = template.evaluateAsString("/Contacts/Contact/Name", request);
        // do something with name
    }

}

4.4. 消息记录和跟踪

在开发或调试 Web 服务时,查看 (SOAP) 消息到达时或发送前的内容会非常有用。Spring Web Services 通过标准的 Commons Logging 接口提供此功能。

确保使用 Commons Logging 1.1 或更高版本。早期版本存在类加载问题,并且不与 Log4J TRACE 级别集成。

要记录所有服务器端消息,请将org.springframework.ws.server.MessageTracing记录器级别设置为DEBUGTRACE。在DEBUG级别上,仅记录有效负载根元素。在TRACE级别上,将记录整个消息内容。如果您只想记录发送的消息,请使用org.springframework.ws.server.MessageTracing.sent记录器。同样,您可以使用org.springframework.ws.server.MessageTracing.received仅记录收到的消息。

在客户端,存在类似的记录器:org.springframework.ws.client.MessageTracing.sentorg.springframework.ws.client.MessageTracing.received.

以下log4j.properties配置文件示例记录了客户端发送消息的全部内容,并且仅记录了客户端接收消息的有效负载根元素。在服务器端,记录了发送和接收消息的有效负载根:

log4j.rootCategory=INFO, stdout
log4j.logger.org.springframework.ws.client.MessageTracing.sent=TRACE
log4j.logger.org.springframework.ws.client.MessageTracing.received=DEBUG

log4j.logger.org.springframework.ws.server.MessageTracing=DEBUG

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p [%c{3}] %m%n

使用此配置,典型输出为:

TRACE [client.MessageTracing.sent] 发送请求 [<SOAP-ENV:Envelope xmlns:SOAP-ENV="...
调试 [server.MessageTracing.received] 收到请求 [SaajSoapMessage {http://example.com}request] ...
调试 [server.MessageTracing.sent] 发送响应 [SaajSoapMessage {http://example.com}response] ...
调试 [client.MessageTracing.received] 收到响应 [SaajSoapMessage {http://example.com}response] ...

5. 使用 Spring-WS 创建 Web 服务

Spring-WS 的服务器端支持是围绕MessageDispatcher将传入消息分派到端点而设计的,具有可配置的端点映射、响应生成和端点拦截。端点通常使用注释进行@Endpoint注释,并具有一种或多种处理方法。这些方法通过检查部分消息(通常是有效负载)来处理传入的 XML 请求消息并创建某种响应。您可以使用另一个注释来注释该方法,通常是@PayloadRoot,以指示它可以处理哪种类型的消息。

Spring-WS 的 XML 处理非常灵活。端点可以从 Spring-WS 支持的大量 XML 处理库中进行选择,包括:

  • DOM 家族:W3C DOM、JDOM、dom4j 和 XOM

  • SAX 或 StAX:为了更快的性能

  • XPath:从消息中提取信息

  • 编组技术(JAXB、Castor、XMLBeans、JiBX 或 XStream):将 XML 转换为对象,反之亦然

5.1。这MessageDispatcher

Spring-WS 的服务器端是围绕一个中心类设计的,该类将传入的 XML 消息分派到端点。Spring-WSMessageDispatcher非常灵活,允许您使用任何类型的类作为端点,只要它可以在 Spring IoC 容器中进行配置。在某种程度上,消息调度程序类似于 Spring 的DispatcherServlet,即 Spring Web MVC 中使用的“前端控制器”。

以下序列图显示了 的处理和调度流程MessageDispatcher

序列

MessageDispatcher设置 a 以供使用并且该特定调度程序收到请求时,将MessageDispatcher开始处理该请求。以下过程描述了如何MessageDispatcher处理请求:

  1. 搜索配置EndpointMapping(s)的适当端点。如果找到端点,则调用与端点(预处理器、后处理器和端点)关联的调用链以创建响应。

  2. 为端点找到合适的适配器。对此适配器的MessageDispatcher委托以调用端点。

  3. 如果返回响应,则会在途中发送。如果没有返回响应(这可能是由于前处理器或后处理器拦截了请求,例如出于安全原因),则不会发送响应。

在处理请求期间抛出的异常被应用程序上下文中声明的任何端点异常解析器拾取。使用这些异常解析器可以定义自定义行为(例如返回 SOAP 错误),以防引发此类异常。

MessageDispatcher几个属性用于设置端点适配器、映射异常解析器。但是,不需要设置这些属性,因为调度程序会自动检测在应用程序上下文中注册的所有类型。只有在需要覆盖检测时才应设置这些属性。

消息调度程序在消息上下文上运行,而不是在特定于传输的输入流和输出流上运行。因此,特定于传输的请求需要读入MessageContext. 对于 HTTP,这是通过 a WebServiceMessageReceiverHandlerAdapter(这是 Spring Web HandlerInterceptor)完成的,因此MessageDispatcher可以连接到标准DispatcherServlet. 但是,有一种更方便的方法可以做到这一点,如图所示MessageDispatcherServlet

5.2. 运输

Spring Web Services 支持多种传输协议。最常见的是 HTTP 传输,它提供了一个自定义 servlet,但您也可以通过 JMS 甚至电子邮件发送消息。

5.2.1。MessageDispatcherServlet

MessageDispatcherServlet是一个标准Servlet,它方便地从标准 Spring Web 扩展DispatcherServlet并包装了MessageDispatcher. 结果,它将这些属性合二为一。作为一个MessageDispatcher,它遵循与上一节中描述的相同的请求处理流程。作为 servlet,在您的 Web 应用程序MessageDispatcherServlet中进行配置。web.xml您希望处理的请求必须通过同一文件MessageDispatcherServlet中的 URL 映射进行映射。web.xml这是标准的 Java EE servlet 配置。以下示例显示了这样的MessageDispatcherServlet声明和映射:

<web-app>

    <servlet>
        <servlet-name>spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

</web-app>

在前面的示例中,所有请求都由spring-ws MessageDispatcherServlet. 这只是设置 Spring Web Services 的第一步,因为 Spring-WS 框架使用的各种组件 bean 也需要进行配置。此配置由标准 Spring XML<bean/>定义组成。因为MessageDispatcherServlet是一个标准的 Spring DispatcherServlet,它会在你的 web 应用程序的目录中查找一个名为 [servlet-name]-servlet.xml 的WEB-INF文件,并在 Spring 容器中创建定义的 bean。在前面的示例中,它查找“/WEB-INF/spring-ws-servlet.xml”。此文件包含所有 Spring Web 服务 bean,例如端点、编组器等。

作为替代方案web.xml,如果您在 Servlet 3+ 环境中运行,您可以通过编程方式配置 Spring-WS。为此,Spring-WS 提供了许多抽象基类,这些基类扩展WebApplicationInitializer了 Spring 框架中的接口。如果您还@Configuration为您的 bean 定义使用类,您应该扩展AbstractAnnotationConfigMessageDispatcherServletInitializer

public class MyServletInitializer
    extends AbstractAnnotationConfigMessageDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{MyRootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MyEndpointConfig.class};
    }

}

在前面的示例中,我们告诉 Spring 端点 bean 定义可以在MyEndpointConfig类(这是一个@Configuration类)中找到。可以在MyRootConfig该类中找到其他 bean 定义(通常是服务、存储库等)。默认情况下,AbstractAnnotationConfigMessageDispatcherServletInitializer将 servlet 映射到两种模式:/services*.wsdl,尽管您可以通过覆盖该getServletMappings()方法来更改它。有关 的编程配置的更多详细信息,请参阅和MessageDispatcherServlet的 Javadoc 。AbstractMessageDispatcherServletInitializerAbstractAnnotationConfigMessageDispatcherServletInitializer

自动 WSDL 暴露

MessageDispatcherServlet自动检测在其 Spring 容器中定义的任何bean WsdlDefinition。所有WsdlDefinition检测到的 bean 也通过WsdlDefinitionHandlerAdapter. 这是通过定义一些 bean 向客户端公开 WSDL 的便捷方式。

作为示例,请考虑在 Spring-WS 配置文件 ( )<static-wsdl>中定义的以下定义。/WEB-INF/[servlet-name]-servlet.xml注意id属性的值,因为它在公开 WSDL 时使用。

<sws:static-wsdl id="orders" location="orders.wsdl"/>

或者,它可以是类中的@Bean方法@Configuration

@Bean
public SimpleWsdl11Definition orders() {
	return new SimpleWsdl11Definition(new ClassPathResource("orders.wsdl"));
}

您可以通过对以下形式的 URL 的请求访问orders.wsdl类路径上文件中定义的 WSDL (根据需要替换主机、端口和 servlet 上下文路径):GET

http://localhost:8080/spring-ws/orders.wsdl
所有WsdlDefinitionbean 定义都通过MessageDispatcherServletbean 名称下的 .wsdl 后缀公开。因此,如果 bean 名称为echo,主机名称为server,并且 Servlet 上下文(war 名称)为spring-ws,则 WSDL 可以在 中找到http://server/spring-ws/echo.wsdl

MessageDispatcherServlet(或者更准确地说是)的另一个很好的特性WsdlDefinitionHandlerAdapter是它可以转换location它公开的所有 WSDL 的值,以反映传入请求的 URL。

请注意,此location转换功能默认关闭。要打开此功能,您需要指定一个初始化参数MessageDispatcherServlet

<web-app>

  <servlet>
    <servlet-name>spring-ws</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    <init-param>
      <param-name>transformWsdlLocations</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>

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

</web-app>

如果使用AbstractAnnotationConfigMessageDispatcherServletInitializer,启用转换就像重写isTransformWsdlLocations()返回方法一样简单true

查阅类上的类级 JavadocWsdlDefinitionHandlerAdapter以了解有关整个转换过程的更多信息。

作为手动编写 WSDL 并使用 公开它的替代方法<static-wsdl>,Spring Web Services 还可以从 XSD 模式生成 WSDL。这是发布 WSDL中所示的方法。下一个应用程序上下文片段展示了如何创建这样一个动态 WSDL 文件:

<sws:dynamic-wsdl id="orders"
    portTypeName="Orders"
    locationUri="http://localhost:8080/ordersService/">
  <sws:xsd location="Orders.xsd"/>
</sws:dynamic-wsdl>

或者,您可以使用 Java@Bean方法:

@Bean
public DefaultWsdl11Definition orders() {
    DefaultWsdl11Definition definition = new DefaultWsdl11Definition();
    definition.setPortTypeName("Orders");
    definition.setLocationUri("http://localhost:8080/ordersService/");
    definition.setSchema(new SimpleXsdSchema(new ClassPathResource("echo.xsd")));

    return definition;
}

<dynamic-wsdl>元素取决于类DefaultWsdl11Definition。此定义类使用org.springframework.ws.wsdl.wsdl11.provider包中的 WSDL 提供程序和ProviderBasedWsdl4jDefinition该类在第一次被请求时生成 WSDL。如有必要,请参阅这些类的类级别 Javadoc 以了解如何扩展此机制。

DefaultWsdl11Definition因此,<dynamic-wsdl>标签)使用约定从 XSD 模式构建 WSDL。它遍历element模式中找到的所有元素并为所有元素创建一个messageoperation接下来,它为所有以定义的请求或响应后缀结尾的消息创建一个 WSDL 。默认请求后缀是Request. 默认响应后缀是Response,尽管可以通过分别设置requestSuffixresponseSuffix属性来更改这些后缀<dynamic-wsdl />。它还基于操作构建portTypebinding和。service

例如,如果我们的Orders.xsd模式定义了GetOrdersRequestandGetOrdersResponse元素,<dynamic-wsdl>创建一个GetOrdersRequestandGetOrdersResponse消息和一个GetOrders操作,它被放入一个Orders端口类型中。

要通过包含或导入使用多个模式,您可以将 Commons XMLSchema 放在类路径上。如果 Commons XMLSchema 在类路径上,则该<dynamic-wsdl>元素遵循所有 XSD 导入,并将它们作为单个 XSD 包含并内联到 WSDL 中。这极大地简化了模式的部署,同时仍然可以单独编辑它们。

尽管在运行时从 XSD 创建 WSDL 很方便,但这种方法有几个缺点。首先,尽管我们尝试在不同版本之间保持 WSDL 生成过程的一致性,但它仍然有可能(稍微)发生变化。其次,生成有点慢,不过,一旦生成,WSDL 会被缓存以供以后参考。

因此,您应该<dynamic-wsdl>只在项目的开发阶段使用。我们建议使用您的浏览器下载生成的 WSDL,将其存储在项目中,并使用<static-wsdl>. 这是真正确保 WSDL 不会随时间变化的唯一方法。

5.2.2. 在一个中连接 Spring-WSDispatcherServlet

作为 的替代方案MessageDispatcherServlet,您可以将 a 连接MessageDispatcher到标准的 Spring-Web MVCDispatcherServlet中。默认情况下,DispatcherServlet只能委托给,但我们可以通过将 a 添加到 servlet 的 Web 应用程序上下文中Controllers来指示它委托给 a :MessageDispatcherWebServiceMessageReceiverHandlerAdapter

<beans>

    <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="defaultHandler" ref="messageDispatcher"/>
    </bean

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>

    ...

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

</beans>

请注意,通过显式添加WebServiceMessageReceiverHandlerAdapter,调度程序 servlet 不会加载默认适配器并且无法处理标准 Spring-MVC @Controllers。因此,我们RequestMappingHandlerAdapter在末尾添加了。

以类似的方式,您可以连接 aWsdlDefinitionHandlerAdapter以确保DispatcherServlet可以处理WsdlDefinition接口的实现:

<beans>

    <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"/>

    <bean class="org.springframework.ws.transport.http.WsdlDefinitionHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
           <props>
             <prop key="*.wsdl">myServiceDefinition</prop>
           </props>
        </property>
        <property name="defaultHandler" ref="messageDispatcher"/>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>

    <bean id="myServiceDefinition" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
       <prop name="wsdl" value="/WEB-INF/myServiceDefintion.wsdl"/>
    </bean>

    ...

</beans>

5.2.3。JMS 传输

Spring Web Services 通过 Spring 框架中提供的 JMS 功能支持服务器端 JMS 处理。Spring Web Services 提供了WebServiceMessageListener插件到MessageListenerContainer. 这个消息监听器需要一个WebServiceMessageFactoryandMessageDispatcher来操作。以下配置示例显示了这一点:

<beans>

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
    </bean>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destinationName" value="RequestQueue"/>
        <property name="messageListener">
            <bean class="org.springframework.ws.transport.jms.WebServiceMessageListener">
                <property name="messageFactory" ref="messageFactory"/>
                <property name="messageReceiver" ref="messageDispatcher"/>
            </bean>
        </property>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>
</beans>

5.2.4。电子邮件传输

除了 HTTP 和 JMS,Spring Web Services 还提供服务器端电子邮件处理。这个功能是通过MailMessageReceiver类提供的。此类监视 POP3 或 IMAP 文件夹,将电子邮件转换为WebServiceMessage,并使用 SMTP 发送任何响应。您可以通过 配置主机名storeUri,指示要监视请求的邮件文件夹(通常是 POP3 或 IMAP 文件夹),以及transportUri指示用于发送响应的服务器(通常是 SMTP 服务器)。

您可以MailMessageReceiver使用可插拔策略配置监控传入消息的方式:MonitoringStrategy. 默认情况下,使用轮询策略,其中每五分钟轮询传入文件夹以查找新邮件。pollingInterval您可以通过设置策略的属性来更改此间隔。默认情况下,所有MonitoringStrategy实现都会删除已处理的消息。您可以通过设置deleteMessages属性来更改此设置。

作为非常低效的轮询方法的替代方法,有一种使用 IMAP IDLE 的监视策略。MailMessageReceiverIDLE 命令是 IMAP 电子邮件协议的可选扩展,它允许邮件服务器向异步发送新消息更新。如果您使用支持 IDLE 命令的 IMAP 服务器,则可以将其ImapIdleMonitoringStrategy插入monitoringStrategy属性。除了支持服务器,您还需要使用 JavaMail 1.4.1 或更高版本。

以下配置显示如何使用服务器端电子邮件支持,覆盖默认轮询间隔以每 30 秒(30.000 毫秒)检查一次:

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="messagingReceiver" class="org.springframework.ws.transport.mail.MailMessageReceiver">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="from" value="Spring-WS SOAP Server &lt;server@example.com&gt;"/>
        <property name="storeUri" value="imap://server:s04p@imap.example.com/INBOX"/>
        <property name="transportUri" value="smtp://smtp.example.com"/>
        <property name="messageReceiver" ref="messageDispatcher"/>
        <property name="monitoringStrategy">
            <bean class="org.springframework.ws.transport.mail.monitor.PollingMonitoringStrategy">
                <property name="pollingInterval" value="30000"/>
            </bean>
        </property>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>
</beans>

5.2.5。嵌入式 HTTP 服务器传输

Spring Web Services 提供基于 Sun 的 JRE 1.6 HTTP 服务器的传输。嵌入式 HTTP 服务器是一个易于配置的独立服务器。它为传统的 servlet 容器提供了一种更轻量级的替代方案。

使用嵌入式 HTTP 服务器时,您不需要外部部署描述符 ( web.xml)。您只需要定义一个服务器实例并将其配置为处理传入请求。Core Spring Framework 中的远程处理模块包含一个方便的 HTTP 服务器工厂 bean SimpleHttpServerFactoryBean:. 最重要的属性是contexts,它将上下文路径映射到相应的HttpHandler实例。

Spring Web Services 提供了两种HttpHandler接口实现:WsdlDefinitionHttpHandlerWebServiceMessageReceiverHttpHandler. 前者将传入的 GET 请求映射到WsdlDefinition. 后者负责处理 Web 服务消息的 POST 请求,因此需要一个WebServiceMessageFactory(通常是 a SaajSoapMessageFactory)和一个WebServiceMessageReceiver(通常是SoapMessageDispatcher)来完成其任务。

为了与 servlet 世界进行类比,该contexts属性在 中扮演 servlet 映射的角色,web.xml并且WebServiceMessageReceiverHttpHandler相当于 a MessageDispatcherServlet

以下片段显示了 HTTP 服务器传输的配置示例:

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="messageReceiver" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings" ref="endpointMapping"/>
    </bean>

    <bean id="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
        <property name="defaultEndpoint" ref="stockEndpoint"/>
    </bean>

    <bean id="httpServer" class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
        <property name="contexts">
            <map>
                <entry key="/StockService.wsdl" value-ref="wsdlHandler"/>
                <entry key="/StockService" value-ref="soapHandler"/>
            </map>
        </property>
    </bean>

    <bean id="soapHandler" class="org.springframework.ws.transport.http.WebServiceMessageReceiverHttpHandler">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="messageReceiver" ref="messageReceiver"/>
    </bean>

    <bean id="wsdlHandler" class="org.springframework.ws.transport.http.WsdlDefinitionHttpHandler">
        <property name="definition" ref="wsdlDefinition"/>
    </bean>
</beans>

有关 的更多信息SimpleHttpServerFactoryBean,请参阅Javadoc

5.2.6。XMPP 传输

Spring Web Services 2.0 引入了对 XMPP 的支持,也称为 Jabber。该支持基于Smack库。

Spring Web Services 对 XMPP 的支持与其他传输非常相似:有XmppMessageSender用于 的aaWebServiceTemplateXmppMessageReceiver用于MessageDispatcher.

以下示例显示了如何设置服务器端 XMPP 组件:

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
        <property name="host" value="jabber.org"/>
        <property name="username" value="username"/>
        <property name="password" value="password"/>
    </bean>

    <bean id="messagingReceiver" class="org.springframework.ws.transport.xmpp.XmppMessageReceiver">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="connection" ref="connection"/>
        <property name="messageReceiver" ref="messageDispatcher"/>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>

</beans>

5.2.7. MTOM

MTOM是一种向 Web 服务发送二进制数据和从 Web 服务发送二进制数据的机制。您可以通过MTOM 示例了解如何使用 Spring WS 实现这一点。

5.3. 端点

端点是 Spring-WS 服务器端支持的核心概念。端点提供对应用程序行为的访问,这通常由业务服务接口定义。端点解释 XML 请求消息并使用该输入(通常)调用业务服务上的方法。该服务调用的结果表示为响应消息。Spring-WS 有各种各样的端点,并使用各种方法来处理 XML 消息和创建响应。

您可以通过使用注释对类进行注释来创建端点@Endpoint。在该类中,您可以使用多种参数类型(例如 DOM 元素、JAXB2 对象等)定义一个或多个处理传入 XML 请求的方法。@PayloadRoot您可以通过使用另一个注解(通常是)来指示方法可以处理的消息类型。

考虑以下示例端点:

package samples;

import org.w3c.dom.Element;

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.soap.SoapHeader;

@Endpoint                                                                                      (1)
public class AnnotationOrderEndpoint {

  private final OrderService orderService;

  @Autowired                                                                                   (2)
  public AnnotationOrderEndpoint(OrderService orderService) {
      this.orderService = orderService;
  }

  @PayloadRoot(localPart = "order", namespace = "http://samples")                              (5)
  public void order(@RequestPayload Element orderElement) {                                    (3)
    Order order = createOrder(orderElement);
    orderService.createOrder(order);
  }

  @PayloadRoot(localPart = "orderRequest", namespace = "http://samples")                       (5)
  @ResponsePayload
  public Order getOrder(@RequestPayload OrderRequest orderRequest, SoapHeader header) {        (4)
    checkSoapHeaderForSomething(header);
    return orderService.getOrder(orderRequest.getId());
  }

  ...

}
1 该类使用 注释@Endpoint,将其标记为 Spring-WS 端点。
2 构造函数带有标记,@Autowired以便将OrderService业务服务注入此端点。
3 order方法将Element(用 注释@RequestPayload)作为参数。这意味着消息的有效负载作为 DOM 元素在此方法上传递。该方法有void返回类型,表示不发送响应消息。有关端点方法的更多信息,请参阅@Endpoint处理方法
4 getOrder方法采用OrderRequest(也用 注释@RequestPayload)作为参数。此参数是 JAXB2 支持的对象(用 注释@XmlRootElement)。这意味着消息的有效负载作为未编组的对象传递给此方法。SoapHeader类型也作为参数给出。在调用时,此参数包含请求消息的 SOAP 标头。该方法还用 注释@ResponsePayload,表示返回值(Order)用作响应消息的有效负载。有关端点方法的更多信息,请参阅@Endpoint处理方法
5 该端点的两个处理方法用 标记@PayloadRoot,表示该方法可以处理哪种类型的请求消息:该getOrder方法是针对具有orderRequest本地名称和http://samples命名空间 URI 的请求调用的。对具有order本地名称的请求调用 order 方法。有关详细信息@PayloadRoot,请参阅端点映射

要启用对@Endpoint相关 Spring-WS 注释的支持,您需要将以下内容添加到 Spring 应用程序上下文中:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  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.xsd
    http://www.springframework.org/schema/web-services
      http://www.springframework.org/schema/web-services/web-services.xsd">

  *<sws:annotation-driven />

</beans>

或者,如果您使用@Configuration类而不是 Spring XML,则可以使用以下命令注释您的配置类@EnableWs

@EnableWs
@Configuration
public class EchoConfig {

    // @Bean definitions go here

}

要自定义@EnableWs配置,您可以实现WsConfigurer,或者更好的是,扩展WsConfigurerAdapter

@Configuration
@EnableWs
@ComponentScan(basePackageClasses = { MyConfiguration.class })
public class MyConfiguration extends WsConfigurerAdapter {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    interceptors.add(new MyInterceptor());
  }

  @Override
  public void addArgumentResolvers(List<MethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(new MyArgumentResolver());
  }

  // More overridden methods ...
}

在接下来的几节中,将对@Endpoint编程模型进行更详细的描述。

端点,与任何其他 Spring Bean 一样,默认情况下被限定为单例。也就是说,每个容器都会创建一个 bean 定义的实例。作为单例意味着多个线程可以同时使用它,因此端点必须是线程安全的。如果您想使用不同的范围,例如原型,请参阅Spring 参考文档
请注意,Spring-WS 中提供的所有抽象基类都是线程安全的,除非在类级别 Javadoc 中另有说明。

5.3.1。@Endpoint处理方法

要让端点实际处理传入的 XML 消息,它需要有一个或多个处理方法。处理方法可以采用广泛的参数和返回类型。但是,它们通常具有一个包含消息有效负载的参数,并且它们返回响应消息的有效负载(如果有)。本节介绍支持哪些参数和返回类型。

为了指示一个方法可以处理哪种类型的消息,该方法通常使用 the@PayloadRoot或注解进行@SoapAction注解。您可以在Endpoint mappings中了解有关这些注释的更多信息。

以下示例显示了一种处理方法:

@PayloadRoot(localPart = "order", namespace = "http://samples")
public void order(@RequestPayload Element orderElement) {
  Order order = createOrder(orderElement);
  orderService.createOrder(order);
}

order方法将Element(用 注释@RequestPayload)作为参数。这意味着消息的有效负载作为 DOM 元素在此方法上传递。该方法有void返回类型,表示不发送响应消息。

处理方法参数

处理方法通常具有一个或多个参数,这些参数引用传入 XML 消息的各个部分。最常见的是,处理方法有一个参数映射到消息的有效负载,但它也可以映射到请求消息的其他部分,例如 SOAP 标头。本节介绍您可以在处理方法签名中使用的参数。

要将参数映射到请求消息的有效负载,您需要使用注解对该参数进行@RequestPayload注解。这个注解告诉 Spring-WS 参数需要绑定到请求负载。

下表描述了支持的参数类型。它显示了支持的类型,参数是否应该用 注释@RequestPayload,以及任何其他注释。

姓名 支持的参数类型 @RequestPayload必需的? 补充说明

TrAX

javax.xml.transform.Source和子接口(DOMSourceSAXSourceStreamSourceStAXSource

是的

默认启用。

W3C DOM

org.w3c.dom.Element

是的

默认启用

dom4j

org.dom4j.Element

是的

当 dom4j 在类路径上时启用。

JDOM

org.jdom.Element

是的

当 JDOM 在类路径上时启用。

XOM

nu.xom.Element

是的

当 XOM 在类路径上时启用。

斯塔克斯

javax.xml.stream.XMLStreamReaderjavax.xml.stream.XMLEventReader

是的

当 StAX 在类路径上时启用。

XPath

任何布尔值、双精度、、、、String或可以由 Spring转换服务org.w3c.Node从 a 转换的类型,并且用.org.w3c.dom.NodeListString@XPathParam

默认启用,请参阅名为XPathParam.

消息上下文

org.springframework.ws.context.MessageContext

默认启用。

肥皂

org.springframework.ws.soap.SoapMessage, org.springframework.ws.soap.SoapBody, org.springframework.ws.soap.SoapEnvelope, org.springframework.ws.soap.SoapHeader, 和org.springframework.ws.soap.SoapHeaderElement`s when used in combination with the `@SoapHeader注释。

默认启用。

JAXB2

javax.xml.bind.annotation.XmlRootElement用和注释的任何类型javax.xml.bind.JAXBElement

是的

当 JAXB2 在类路径上时启用。

OXM

Spring OXM 支持的任何类型Unmarshaller

是的

unmarshaller指定属性时启用<sws:annotation-driven/>

接下来的几个示例显示了可能的方法签名。使用请求消息的有效负载作为 DOM 调用以下方法org.w3c.dom.Element

public void handle(@RequestPayload Element element)

使用请求消息的有效负载作为javax.xml.transform.dom.DOMSource. 该header参数绑定到请求消息的 SOAP 标头。

public void handle(@RequestPayload DOMSource domSource, SoapHeader header)

调用以下方法,请求消息的有效负载被解组为 a MyJaxb2Object(用 注释@XmlRootElement)。消息的有效负载也以 DOM 形式给出Element。整个消息上下文作为第三个参数传递。

public void handle(@RequestPayload MyJaxb2Object requestObject, @RequestPayload Element element, Message messageContext)

如您所见,在定义如何处理方法签名时有很多可能性。您甚至可以扩展此机制以支持您自己的参数类型。请参阅 JavadocDefaultMethodEndpointAdapterMethodArgumentResolver了解如何操作。

@XPathParam

一种参数类型需要一些额外的解释:@XPathParam. 这里的想法是使用 XPath 表达式注释一个或多个方法参数,并且每个这样的注释参数都绑定到表达式的评估。以下示例显示了如何执行此操作:

package samples;

import javax.xml.transform.Source;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.Namespace;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.XPathParam;

@Endpoint
public class AnnotationOrderEndpoint {

  private final OrderService orderService;

  public AnnotationOrderEndpoint(OrderService orderService) {
    this.orderService = orderService;
  }

  @PayloadRoot(localPart = "orderRequest", namespace = "http://samples")
  @Namespace(prefix = "s", uri="http://samples")
  public Order getOrder(@XPathParam("/s:orderRequest/@id") int orderId) {
    Order order = orderService.getOrder(orderId);
    // create Source from order and return it
  }

}

因为我们s在 XPath 表达式中使用了前缀,所以我们必须将它绑定到http://samples命名空间。这是通过@Namespace注释完成的。或者,我们可以将此注释放置在类型级别上,以便为所有处理程序方法使用相同的命名空间映射,甚至可以在包级别 (in package-info.java) 上将其用于多个端点。

通过使用@XPathParam,您可以绑定到 XPath 支持的所有数据类型:

  • boolean或者Boolean

  • double或者Double

  • String

  • Node

  • NodeList

除了这个列表之外,您还可以使用任何可以String通过 Spring转换服务从 a 转换的类型。

处理方法返回类型

要发送响应消息,处理需要指定返回类型。如果不需要响应消息,该方法可以声明void返回类型。最常见的是,返回类型用于创建响应消息的有效负载。但是,您也可以映射到响应消息的其他部分。本节描述您可以在处理方法签名中使用的返回类型。

要将返回值映射到响应消息的有效负载,您需要使用注解对方法进行@ResponsePayload注解。这个注解告诉 Spring-WS 返回值需要绑定到响应负载。

下表描述了支持的返回类型。它显示了支持的类型,参数是否应该用 注释@ResponsePayload,以及任何其他注释。

姓名 支持的返回类型 @ResponsePayload必需的? 补充说明

没有反应

void

默认启用。

TrAX

javax.xml.transform.Source和子接口(DOMSourceSAXSourceStreamSourceStAXSource

是的

默认启用。

W3C DOM

org.w3c.dom.Element

是的

默认启用

dom4j

org.dom4j.Element

是的

当 dom4j 在类路径上时启用。

JDOM

org.jdom.Element

是的

当 JDOM 在类路径上时启用。

XOM

nu.xom.Element

是的

当 XOM 在类路径上时启用。

JAXB2

javax.xml.bind.annotation.XmlRootElement用和注释的任何类型javax.xml.bind.JAXBElement

是的

当 JAXB2 在类路径上时启用。

OXM

Spring OXM 支持的任何类型Marshaller

是的

marshaller指定属性时启用<sws:annotation-driven/>

在定义处理方法签名时有很多可能性。甚至可以扩展此机制以支持您自己的参数类型。请参阅 的类级别 JavadocDefaultMethodEndpointAdapterMethodReturnValueHandler了解如何操作。

5.4. 端点映射

端点映射负责将传入消息映射到适当的端点。默认情况下会启用一些端点映射——例如,PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping. 但是,我们首先需要检查 a 的一般概念EndpointMapping

AnEndpointMapping传递 a EndpointInvocationChain,其中包含与传入请求匹配的端点,还可能包含应用于请求和响应的端点拦截器列表。当请求进来时,将MessageDispatcher其交给端点映射,让它检查请求并提出适当的EndpointInvocationChain. 然后MessageDispatcher调用链中的端点和任何拦截器。

可以选择包含拦截器(反过来,可以操纵请求、响应或两者)的可配置端点映射的概念非常强大。许多支持功能可以内置到自定义EndpointMapping实现中。例如,自定义端点映射不仅可以基于消息的内容,还可以基于特定的 SOAP 标头(或者实际上是多个 SOAP 标头)来选择端点。

大多数端点映射都继承自AbstractEndpointMapping,它提供了一个“拦截器”属性,即要使用的拦截器列表。EndpointInterceptors拦截请求 -EndpointInterceptor接口中进行了讨论。此外,还有defaultEndpoint,这是在此端点映射未产生匹配端点时使用的默认端点。

Endpoints中所述,该@Endpoint样式允许您在一个端点类中处理多个请求。这是 的责任MethodEndpointMapping。此映射确定要为传入的请求消息调用哪个方法。

有两个端点映射可以将请求定向到方法:PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping您可以通过<sws:annotation-driven/>在应用程序上下文中使用来启用这两种方法。

使用带有and元素PayloadRootAnnotationMethodEndpointMapping@PayloadRoot注释来标记具有特定限定名称的方法。每当带有此有效负载根元素的限定名称的消息进入时,就会调用该方法。例如,请参见上文localPartnamespace

或者,SoapActionAnnotationMethodEndpointMapping使用@SoapAction注释来标记具有特定 SOAP 操作的方法。每当带有此SOAPAction标头的消息进入时,都会调用该方法。

5.4.1。WS-寻址

WS-Addressing 指定了一种与传输无关的路由机制。它基于ToActionSOAP 标头,它们分别指示 SOAP 消息的目的地和意图。此外,WS-Addressing 允许您定义返回地址(用于正常消息和故障)和唯一消息标识符,可用于关联。有关 WS-Addressing 的更多信息,请参阅https://en.wikipedia.org/wiki/WS-Addressing。以下示例显示了 WS-Addressing 消息:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
    xmlns:wsa="http://www.w3.org/2005/08/addressing">
  <SOAP-ENV::Header>
    <wsa:MessageID>urn:uuid:21363e0d-2645-4eb7-8afd-2f5ee1bb25cf</wsa:MessageID>
    <wsa:ReplyTo>
      <wsa:Address>http://example.com/business/client1</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To S:mustUnderstand="true">http://example/com/fabrikam</wsa:To>
    <wsa:Action>http://example.com/fabrikam/mail/Delete</wsa:Action>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <f:Delete xmlns:f="http://example.com/fabrikam">
      <f:maxCount>42</f:maxCount>
    </f:Delete>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

在前面的示例中,目标设置为http://example/com/fabrikam,而操作设置为http://example.com/fabrikam/mail/Delete。此外,还有一个消息标识符和一个回复地址。默认情况下,该地址为“匿名”地址,表示应使用与请求相同的通道(即 HTTP 响应)发送响应,但也可以是其他地址,如本例所示。

在 Spring Web Services 中,WS-Addressing 被实现为端点映射。通过使用此映射,您可以将 WS-Addressing 操作与端点相关联,类似于SoapActionAnnotationMethodEndpointMapping前面所述。

使用AnnotationActionEndpointMapping

AnnotationActionEndpointMapping类似于SoapActionAnnotationMethodEndpointMappingWS-Addressing 头而不是 SOAP Action 传输头。

要使用AnnotationActionEndpointMapping,请使用注解对处理方法进行@Action注解,类似于处理方法端点映射中描述的@PayloadRoot和注解。以下示例显示了如何执行此操作:@SoapAction@Endpoint

package samples;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.soap.addressing.server.annotation.Action

@Endpoint
public class AnnotationOrderEndpoint {
    private final OrderService orderService;

    public AnnotationOrderEndpoint(OrderService orderService) {
        this.orderService = orderService;
    }

    @Action("http://samples/RequestOrder")
    public Order getOrder(OrderRequest orderRequest) {
        return orderService.getOrder(orderRequest.getId());
    }

    @Action("http://samples/CreateOrder")
    public void order(Order order) {
        orderService.createOrder(order);
    }

}

前面的映射将具有 WS-AddressingAction的请求路由http://samples/RequestOrder到该getOrder方法。请求http://samples/CreateOrder被路由到order方法..

默认情况下,AnnotationActionEndpointMapping支持 1.0(2006 年 5 月)和 2004 年 8 月版本的 WS-Addressing。这两个版本最受欢迎,并且可与 Axis 1 和 2、JAX-WS、XFire、Windows Communication Foundation (WCF) 和 Windows Services Enhancements (WSE) 3.0 互操作。如有必要,可以将规范的特定版本注入到versions属性中。

除了@Action注解之外,您还可以使用注解对类进行@Address注解。如果设置,则将该值与To传入消息的标头属性进行比较。

最后,还有一个messageSenders属性,它是向非匿名、越界地址发送响应消息所必需的。您可以MessageSender在此属性中设置实现,就像在WebServiceTemplate. 请参阅URI 和传输

5.4.2. 拦截请求——EndpointInterceptor接口

端点映射机制具有端点拦截器的概念。当您想将特定功能应用于某些请求时,这些功能非常有用——例如,处理与安全相关的 SOAP 标头或记录请求和响应消息。

<sws:interceptors>端点拦截器通常通过在应用程序上下文中使用元素来定义。在此元素中,您可以定义应用于该应用程序上下文中定义的所有端点的端点拦截器 bean。或者,您可以使用<sws:payloadRoot><sws:soapAction>元素来指定拦截器应应用的有效负载根名称或 SOAP 操作。以下示例显示了如何执行此操作:

<sws:interceptors>
  <bean class="samples.MyGlobalInterceptor"/>
  <sws:payloadRoot namespaceUri="http://www.example.com">
    <bean class="samples.MyPayloadRootInterceptor"/>
  </sws:payloadRoot>
  <sws:soapAction value="http://www.example.com/SoapAction">
    <bean class="samples.MySoapActionInterceptor1"/>
    <ref bean="mySoapActionInterceptor2"/>
  </sws:soapAction>
</sws:interceptors>

<bean id="mySoapActionInterceptor2" class="samples.MySoapActionInterceptor2"/>

在前面的示例中,我们定义了一个“全局”拦截器 ( MyGlobalInterceptor) 来拦截所有请求和响应。我们还定义了一个拦截器,该拦截器仅适用于具有http://www.example.com作为有效负载根命名空间的 XML 消息。除了 之外,我们还可以定义一个localPart属性namespaceUri来进一步限制拦截器适用的消息。最后,我们定义了两个在消息具有http://www.example.com/SoapActionSOAP 操作时应用的拦截器。注意第二个拦截器实际上是对<interceptors>元素外部 bean 定义的引用。您可以在<interceptors>元素内的任何位置使用 bean 引用。

当您使用@Configuration类时,您可以扩展 fromWsConfigurerAdapter以添加拦截器:

@Configuration
@EnableWs
public class MyWsConfiguration extends WsConfigurerAdapter {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    interceptors.add(new MyPayloadRootInterceptor());
  }

}

拦截器必须实现包中的EndpointInterceptor接口org.springframework.ws.server。该接口定义了三种方法,一种可用于在处理实际端点之前处理请求消息,一种可用于处理正常响应消息,另一种可用于处理故障消息。处理完端点后调用后两个。这三种方法应该提供足够的灵活性来进行各种预处理和后处理。

拦截器上的handleRequest(..)方法返回一个布尔值。您可以使用此方法中断或继续处理调用链。当此方法返回true时,端点处理链将继续。当它返回时falseMessageDispatcher解释这意味着拦截器本身已经处理好事情并且不会继续处理其他拦截器和调用链中的实际端点。handleResponse(..)andhandleFault(..)方法也有一个布尔返回值。当这些方法返回false时,响应将不会被发送回客户端。

EndpointInterceptor您可以在 Web 服务中使用许多标准实现。此外,还有XwsSecurityInterceptor,在 中描述XwsSecurityInterceptor

PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor

在开发 Web 服务时,记录传入和传出的 XML 消息会很有用。Spring WS 通过PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor类促进了这一点。前者仅将消息的有效负载记录到 Commons Logging Log。后者记录整个 SOAP 信封,包括 SOAP 标头。以下示例显示了如何PayloadLoggingInterceptor在端点映射中定义:

  <sws:interceptors>
    <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
  </sws:interceptors>

这两个拦截器都有两个属性,logRequestlogResponse,可以设置false为禁用请求或响应消息的日志记录。

WsConfigurerAdapter如前所述,您也可以使用该方法PayloadLoggingInterceptor

PayloadValidatingInterceptor

使用契约优先开发风格的好处之一是我们可以使用模式来验证传入和传出的 XML 消息。Spring-WS 通过PayloadValidatingInterceptor. 此拦截器需要一个或多个 W3C XML 或 RELAX NG 模式的引用,并且可以设置为验证请求、响应或两者。

请注意,请求验证听起来可能是个好主意,但它使生成的 Web 服务非常严格。通常,请求是否验证并不重要,只有端点能够获得足够的信息来完成请求。验证响应是一个好主意,因为端点应该遵守它的模式。记住 Postel 定律:“在你所做的事情上保持保守;在你接受他人的事情上保持自由。”

以下示例使用PayloadValidatingInterceptor. 在此示例中,我们使用架构/WEB-INF/orders.xsd来验证响应而不是请求。请注意,PayloadValidatingInterceptor还可以通过设置schemas属性来接受多个模式。

<bean id="validatingInterceptor"
        class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
    <property name="schema" value="/WEB-INF/orders.xsd"/>
    <property name="validateRequest" value="false"/>
    <property name="validateResponse" value="true"/>
</bean>

当然,您也可以使用WsConfigurerAdapter前面所述的方法PayloadValidatingInterceptor

使用PayloadTransformingInterceptor

为了将有效负载转换为另一种 XML 格式,Spring Web Services 提供了PayloadTransformingInterceptor. 此端点拦截器基于 XSLT 样式表,在支持 Web 服务的多个版本时特别有用,因为您可以将旧消息格式转换为新格式。以下示例使用PayloadTransformingInterceptor

<bean id="transformingInterceptor"
        class="org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor">
    <property name="requestXslt" value="/WEB-INF/oldRequests.xslt"/>
    <property name="responseXslt" value="/WEB-INF/oldResponses.xslt"/>
</bean>

在前面的示例中,我们使用 转换请求,使用 转换/WEB-INF/oldRequests.xslt响应消息/WEB-INF/oldResponses.xslt。请注意,由于端点拦截器是在端点映射级别注册的,因此您可以创建一个应用于“旧式”消息的端点映射并将拦截器添加到该映射中。因此,转换仅适用于这些“旧式”消息。

WsConfigurerAdapter如前所述,您也可以使用该方法PayloadTransformingInterceptor

5.5. 处理异常

Spring-WS 提供EndpointExceptionResolvers了缓解在匹配请求的端点处理消息时发生意外异常的痛苦。端点异常解析器有点类似于可以在 Web 应用程序描述符中定义的异常映射web.xml。但是,它们提供了一种更灵活的方式来处理异常。它们提供有关引发异常时调用的端点的信息。此外,处理异常的程序化方式为您提供了更多选项来正确响应。您可以通过任何方式处理异常,而不是通过提供异常和堆栈跟踪来暴露应用程序的内部结构——例如,通过返回带有特定故障代码和字符串的 SOAP 故障。

端点异常解析器由 自动获取MessageDispatcher,因此不需要显式配置。

除了实现EndpointExceptionResolver接口(这只是实现resolveException(MessageContext, endpoint, Exception)方法的问题)之外,您还可以使用提供的实现之一。最简单的实现是SimpleSoapExceptionResolver,它创建一个 SOAP 1.1 Server 或 SOAP 1.2 Receiver 故障并将异常消息用作故障字符串。这SimpleSoapExceptionResolver是默认值,但可以通过显式添加另一个解析器来覆盖它。

5.5.1。SoapFaultMappingExceptionResolver

SoapFaultMappingExceptionResolver是一个更复杂的实现。此解析器允许您获取可能引发的任何异常的类名并将其映射到 SOAP 故障:

<beans>
    <bean id="exceptionResolver"
        class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
        <property name="defaultFault" value="SERVER"/>
        <property name="exceptionMappings">
            <value>
                org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request
            </value>
        </property>
    </bean>
</beans>

键值和默认端点使用 的格式faultCode,faultString,locale,其中只需要故障代码。如果未设置故障字符串,则默认为异常消息。如果未设置语言,则默认为英语。上述配置将类型异常映射ValidationFailureException到故障字符串为 的客户端 SOAP 故障Invalid request,如下所示:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
       <SOAP-ENV:Fault>
           <faultcode>SOAP-ENV:Client</faultcode>
           <faultstring>Invalid request</faultstring>
       </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

如果发生任何其他异常,则返回默认故障:以异常消息作为故障字符串的服务器端故障。

5.5.2. 使用SoapFaultAnnotationExceptionResolver

您还可以使用注释对异常类进行@SoapFault注释,以指示在抛出该异常时应返回的 SOAP 错误。要获取这些注释,您需要将 添加SoapFaultAnnotationExceptionResolver到您的应用程序上下文中。注释的元素包括故障代码枚举、故障字符串或原因以及语言。以下示例显示了此类异常:

package samples;

import org.springframework.ws.soap.server.endpoint.annotation.FaultCode;
import org.springframework.ws.soap.server.endpoint.annotation.SoapFault;

@SoapFault(faultCode = FaultCode.SERVER)
public class MyBusinessException extends Exception {

    public MyClientException(String message) {
        super(message);
    }
}

每当在端点调用期间MyBusinessException与构造函数字符串一起抛出"Oops!"时,它都会导致以下响应:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
       <SOAP-ENV:Fault>
           <faultcode>SOAP-ENV:Server</faultcode>
           <faultstring>Oops!</faultstring>
       </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

5.6. 服务器端测试

在测试 Web 服务端点时,您有两种可能的方法:

  • 编写单元测试,您可以在其中提供(模拟)参数供端点使用。

    这种方法的优点是它很容易完成(尤其是对于用 注释的类@Endpoint)。缺点是您没有真正测试通过网络发送的 XML 消息的确切内容。

  • 编写集成测试,它会测试消息的内容。

第一种方法可以通过 EasyMock、JMock 等模拟框架轻松实现。下一节重点介绍使用 Spring Web Services 2.0 中引入的测试功能编写集成测试。

5.6.1。编写服务器端集成测试

Spring Web Services 2.0 引入了对创建端点集成测试的支持。在此上下文中,端点是处理 (SOAP) 消息的类(请参阅端点)。

集成测试支持存在于org.springframework.ws.test.server包中。该包中的核心类是MockWebServiceClient. 基本思想是该客户端创建一个请求消息,然后将其发送到在标准MessageDispatcherServlet应用程序上下文中配置的端点(请参阅 参考资料MessageDispatcherServlet)。这些端点处理消息并创建响应。然后,客户收到此响应并根据注册的期望对其进行验证。

的典型用法MockWebServiceClient是: .

  1. MockWebServiceClient通过调用MockWebServiceClient.createClient(ApplicationContext)or创建一个实例MockWebServiceClient.createClient(WebServiceMessageReceiver, WebServiceMessageFactory)

  2. 通过调用发送请求消息sendRequest(RequestCreator),可能使用中RequestCreator提供的默认实现RequestCreators(可以静态导入)。

  3. 通过调用设置响应期望andExpect(ResponseMatcher),可能使用ResponseMatcher提供的默认实现ResponseMatchers(可以静态导入)。andExpect(ResponseMatcher)可以通过链接调用来设置多个期望。

请注意,MockWebServiceClient(和相关的类)提供了“流利的”API,因此您通常可以使用 IDE 中的代码完成功能来指导您完成设置模拟服务器的过程。
另请注意,您可以在单元测试中依赖 Spring Web Services 中可用的标准日志记录功能。有时,检查请求或响应消息以找出特定测试失败的原因可能很有用。有关详细信息,请参阅消息记录和跟踪

例如,考虑以下 Web 服务端点类:

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

@Endpoint                                                                (1)
public class CustomerEndpoint {

  @ResponsePayload                                                       (2)
  public CustomerCountResponse getCustomerCount(                         (2)
      @RequestPayload CustomerCountRequest request) {                    (2)
    CustomerCountResponse response = new CustomerCountResponse();
    response.setCustomerCount(10);
    return response;
  }

}
1 CustomerEndpoint用 注释的in @Endpoint。请参阅端点
2 getCustomerCount()方法将 aCustomerCountRequest作为其参数并返回 a CustomerCountResponse。这两个类都是编组器支持的对象。例如,它们可以具有@XmlRootElementJAXB2 支持的注释。

以下示例显示了 的典型测试CustomerEndpoint

import javax.xml.transform.Source;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.xml.transform.StringSource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.ws.test.server.MockWebServiceClient;                       (1)
import static org.springframework.ws.test.server.RequestCreators.*;                   (1)
import static org.springframework.ws.test.server.ResponseMatchers.*;                  (1)

@RunWith(SpringJUnit4ClassRunner.class)                                               (2)
@ContextConfiguration("spring-ws-servlet.xml")                                        (2)
public class CustomerEndpointIntegrationTest {

  @Autowired
  private ApplicationContext applicationContext;                                      (3)

  private MockWebServiceClient mockClient;

  @Before
  public void createClient() {
    mockClient = MockWebServiceClient.createClient(applicationContext);               (4)
  }

  @Test
  public void customerEndpoint() throws Exception {
    Source requestPayload = new StringSource(
      "<customerCountRequest xmlns='http://springframework.org/spring-ws'>" +
        "<customerName>John Doe</customerName>" +
      "</customerCountRequest>");
    Source responsePayload = new StringSource(
      "<customerCountResponse xmlns='http://springframework.org/spring-ws'>" +
        "<customerCount>10</customerCount>" +
      "</customerCountResponse>");

    mockClient.sendRequest(withPayload(requestPayload)).                              (5)
      andExpect(payload(responsePayload));                                            (5)
  }
}
1 CustomerEndpointIntegrationTest导入和MockWebServiceClient静态导入RequestCreatorsResponseMatchers
2 此测试使用 Spring Framework 中提供的标准测试工具。这不是必需的,但通常是设置测试的最简单方法。
3 应用程序上下文是标准 Spring-WS 应用程序上下文(请参阅 参考资料MessageDispatcherServlet),从spring-ws-servlet.xml. 在这种情况下,应用程序上下文包含一个 bean 定义CustomerEndpoint(或者可能<context:component-scan />使用了 a)。
4 在一个方法中,我们使用工厂方法@Before创建一个。MockWebServiceClientcreateClient
5 我们通过调用静态导入提供的sendRequest()a来发送请求(请参阅使用)。 withPayload() RequestCreatorRequestCreatorsRequestCreatorRequestCreators

我们还通过调用静态导入提供的andExpect()a来设置响应期望(请参阅使用and)。payload() ResponseMatcherResponseMatchersResponseMatcherResponseMatchers

这部分测试可能看起来有点混乱,但 IDE 的代码完成功能很有帮助。键入 后sendRequest(,您的 IDE 可以为您提供可能的请求创建策略列表,前提是您静态导入了RequestCreators. andExpect()如果您是静态导入的,这同样适用于ResponseMatchers.

5.6.2. 使用RequestCreatorRequestCreators

最初,MockWebServiceClient需要创建一个请求消息供端点消费。RequestCreator客户端为此目的使用策略接口:

public interface RequestCreator {

  WebServiceMessage createRequest(WebServiceMessageFactory messageFactory)
    throws IOException;

}

您可以编写自己的此接口实现,使用消息工厂创建请求消息,但您当然不必这样做。该类RequestCreators提供了一种RequestCreator基于方法中给定有效负载创建的withPayload()方法。您通常静态导入RequestCreators.

5.6.3. 使用ResponseMatcherResponseMatchers

当请求消息已经被端点处理并接收到响应时,MockWebServiceClient可以验证该响应消息是否满足某些期望。ResponseMatcher客户端为此目的使用策略接口:

public interface ResponseMatcher {

    void match(WebServiceMessage request,
               WebServiceMessage response)
      throws IOException, AssertionError;

}

再一次,您可以编写自己的此接口实现,AssertionError当消息不符合您的期望时抛出实例,但您当然不必这样做,因为ResponseMatchers该类提供了标准ResponseMatcher实现供您在测试中使用。您通常静态导入此类。

该类ResponseMatchers提供以下响应匹配器:

ResponseMatchers方法 描述

payload()

期望给定的响应负载。

validPayload()

期望响应有效负载针对给定的 XSD 架构进行验证。

xpath()

期望给定的 XPath 表达式存在、不存在或计算为给定值。

soapHeader()

期望响应消息中存在给定的 SOAP 标头。

noFault()

期望响应消息不包含 SOAP 错误。

mustUnderstandFault(), clientOrSenderFault(),serverOrReceiverFault()versionMismatchFault()

期望响应消息包含特定的 SOAP 故障。

andExpect()您可以通过链接调用来设置多个响应预期:

mockClient.sendRequest(...).
 andExpect(payload(expectedResponsePayload)).
 andExpect(validPayload(schemaResource));

有关由 提供的响应匹配器的更多信息ResponseMatchers,请参阅Javadoc

6. 在客户端使用 Spring Web Services

Spring-WS 提供了一个客户端 Web 服务 API,它允许对 Web 服务进行一致的、XML 驱动的访问。它还迎合编组器和解组器的使用,以便您的服务层代码可以专门处理 Java 对象。

org.springframework.ws.client.core包提供了使用客户端访问 API 的核心功能。它包含简化 Web 服务使用的模板类,就像 SpringJdbcTemplate为 JDBC 所做的核心一样。Spring 模板类的共同设计原则是提供帮助方法来执行常见操作,并且对于更复杂的用法,委托给用户实现的回调接口。Web 服务模板遵循相同的设计。这些类提供了各种方便的方法

  • 发送和接收 XML 消息

  • 在发送之前将对象编组为 XML

  • 允许多种运输方式

6.1。使用客户端 API

本节介绍如何使用客户端 API。有关如何使用服务器端 API,请参阅使用 Spring-WS 创建 Web 服务

6.1.1.WebServiceTemplate

WebServiceTemplate是 Spring-WS 中客户端 Web 服务访问的核心类。它包含用于发送Source对象和接收响应消息的方法SourceResult。此外,它可以在通过传输发送对象之前将对象编组为 XML,并将任何响应 XML 再次解组为对象。

URI 和传输

该类WebServiceTemplate使用 URI 作为消息目的地。您可以defaultUri在模板本身上设置属性,也可以在调用模板上的方法时显式提供 URI。URI 被解析为WebServiceMessageSender,它负责跨传输层发送 XML 消息。messageSender您可以使用类的或messageSenders属性设置一个或多个消息发送者WebServiceTemplate

HTTP 传输

WebServiceMessageSender通过 HTTP 发送消息的接口有两种实现。默认实现是HttpUrlConnectionMessageSender,它使用 Java 本身提供的工具。另一种方法是HttpComponentsMessageSender,它使用Apache HttpComponents HttpClient。如果您需要更高级且易于使用的功能(例如身份验证、HTTP 连接池等),请使用后者。

要使用 HTTP 传输,请将 设置defaultUri为类似http://example.com/services或提供uri其中一种方法的参数。

以下示例显示如何使用 HTTP 传输的默认配置:

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="defaultUri" value="http://example.com/WebService"/>
    </bean>

</beans>

以下示例显示如何覆盖默认配置以及如何使用 Apache HttpClient 通过 HTTP 身份验证进行身份验证:

<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
    <constructor-arg ref="messageFactory"/>
    <property name="messageSender">
        <bean class="org.springframework.ws.transport.http.HttpComponentsMessageSender">
            <property name="credentials">
                <bean class="org.apache.http.auth.UsernamePasswordCredentials">
                    <constructor-arg value="john:secret"/>
                </bean>
            </property>
        </bean>
    </property>
    <property name="defaultUri" value="http://example.com/WebService"/>
</bean>
JMS 传输

为了通过 JMS 发送消息,Spring Web Services 提供了JmsMessageSender. 此类使用 Spring 框架的工具将 转换WebServiceMessage为 JMS ,在orMessage上发送它,并接收响应(如果有)。QueueTopic

要使用JmsMessageSender,您需要将defaultUrioruri参数设置为 JMS URI,它至少包含jms:前缀和目标名称。JMS URI 的一些示例是:jms:SomeQueuejms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENTjms:RequestQueue?replyToName=ResponseName. 有关此 URI 语法的更多信息,请参阅JmsMessageSender.

默认情况下,JmsMessageSender发送 JMS ,但您可以使用 JMS URI 上的参数BytesMessage覆盖它以使用- 例如,. 请注意,这是首选类型,因为不可靠地支持附件和字符编码。TextMessagesmessageTypejms:Queue?messageType=TEXT_MESSAGEBytesMessagesTextMessages

以下示例显示了如何将 JMS 传输与 ActiveMQ 连接工厂结合使用:

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
    </bean>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.jms.JmsMessageSender">
                <property name="connectionFactory" ref="connectionFactory"/>
            </bean>
        </property>
        <property name="defaultUri" value="jms:RequestQueue?deliveryMode=NON_PERSISTENT"/>
    </bean>

</beans>
电子邮件传输

Spring Web Services 还提供了电子邮件传输,您可以使用它通过 SMTP 发送 Web 服务消息并通过 POP3 或 IMAP 检索它们。客户端电子邮件功能包含在MailMessageSender该类中。此类根据请求创建电子邮件消息WebServiceMessage并通过 SMTP 发送。然后它等待响应消息到达传入的 POP3 或 IMAP 服务器。

要使用MailMessageSender,请将defaultUrioruri参数设置为mailtoURI - 例如,mailto:john@example.comor mailto:server@localhost?subject=SOAP%20Test。确保消息发送者正确配置了 a transportUri,它指示用于发送请求的服务器(通常是 SMTP 服务器)和 a storeUri,它指示轮询响应的服务器(通常是 POP3 或 IMAP 服务器)。

以下示例显示了如何使用电子邮件传输:

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.mail.MailMessageSender">
                <property name="from" value="Spring-WS SOAP Client &lt;client@example.com&gt;"/>
                <property name="transportUri" value="smtp://client:s04p@smtp.example.com"/>
                <property name="storeUri" value="imap://client:s04p@imap.example.com/INBOX"/>
            </bean>
        </property>
        <property name="defaultUri" value="mailto:server@example.com?subject=SOAP%20Test"/>
    </bean>

</beans>
XMPP 传输

Spring Web Services 2.0 引入了 XMPP (Jabber) 传输,您可以使用它通过 XMPP 发送和接收 Web 服务消息。客户端 XMPP 功能包含在XmppMessageSender该类中。此类从请求中创建 XMPP 消息WebServiceMessage并通过 XMPP 发送它。然后它侦听响应消息的到达。

要使用XmppMessageSender,请将defaultUrioruri参数设置为xmppURI - 例如xmpp:johndoe@jabber.org. 发件人还需要一个XMPPConnectionto work,可以使用org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean.

以下示例显示了如何使用 XMPP 传输:

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
        <property name="host" value="jabber.org"/>
        <property name="username" value="username"/>
        <property name="password" value="password"/>
    </bean>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.xmpp.XmppMessageSender">
                <property name="connection" ref="connection"/>
            </bean>
        </property>
        <property name="defaultUri" value="xmpp:user@jabber.org"/>
    </bean>

</beans>
消息工厂

除了消息发送者之外,还WebServiceTemplate需要一个 Web 服务消息工厂。SOAP 有两个消息工厂:SaajSoapMessageFactoryAxiomSoapMessageFactory. 如果没有指定消息工厂(通过设置messageFactory属性),Spring-WSSaajSoapMessageFactory默认使用。

6.1.2. 发送和接收WebServiceMessage

包含许多方便的WebServiceTemplate方法来发送和接收 Web 服务消息。有接受和返回 a 的方法和返回 a 的Source方法Result。此外,还有一些方法可以将对象编组和解组到 XML。以下示例将简单的 XML 消息发送到 Web 服务:

import java.io.StringReader;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.transport.WebServiceMessageSender;

public class WebServiceClient {

    private static final String MESSAGE =
        "<message xmlns=\"http://tempuri.org\">Hello, Web Service World</message>";

    private final WebServiceTemplate webServiceTemplate = new WebServiceTemplate();

    public void setDefaultUri(String defaultUri) {
        webServiceTemplate.setDefaultUri(defaultUri);
    }

    // send to the configured default URI
    public void simpleSendAndReceive() {
        StreamSource source = new StreamSource(new StringReader(MESSAGE));
        StreamResult result = new StreamResult(System.out);
        webServiceTemplate.sendSourceAndReceiveToResult(source, result);
    }

    // send to an explicit URI
    public void customSendAndReceive() {
        StreamSource source = new StreamSource(new StringReader(MESSAGE));
        StreamResult result = new StreamResult(System.out);
        webServiceTemplate.sendSourceAndReceiveToResult("http://localhost:8080/AnotherWebService",
            source, result);
    }

}
<beans xmlns="http://www.springframework.org/schema/beans">

    <bean id="webServiceClient" class="WebServiceClient">
        <property name="defaultUri" value="http://localhost:8080/WebService"/>
    </bean>

</beans>

前面的示例使用WebServiceTemplate向位于http://localhost:8080/WebService(在simpleSendAndReceive()方法的情况下)的 Web 服务发送“Hello, World”消息并将结果写入控制台。使用WebServiceTemplate默认 URI 注入,因为在 Java 代码中没有显式提供 URI。

请注意,WebServiceTemplate一旦配置该类是线程安全的(假设它的所有依赖项也是线程安全的,Spring-WS 附带的所有依赖项都是这种情况),因此多个对象可以使用同一个共享WebServiceTemplate实例。公开了WebServiceTemplate一个零参数构造函数messageFactorymessageSenderbean 属性,您可以使用它们来构造实例(通过使用 Spring 容器或纯 Java 代码)。或者,考虑从 Spring-WS 的便利基类派生WebServiceGatewaySupport,它公开了便利的 bean 属性以实现轻松配置。(您不必扩展此基类。它仅作为便利类提供。)

6.1.3. 发送和接收 POJO——编组和解组

为了便于发送普通的 Java 对象,WebServiceTemplate有许多send(..)方法可以将 anObject作为消息数据内容的参数。marshalSendAndReceive(..)类中的方法WebServiceTemplate将请求对象到 XML 的转换委托给 aMarshaller并将响应 XML 到对象的转换委托给Unmarshaller. (有关 marshalling 和 unmarshaller 的更多信息,请参阅Spring Framework 参考文档。)通过使用 marshaller,您的应用程序代码可以专注于正在发送或接收的业务对象,而不必关心它如何表示为的细节XML。marshaller要使用编组功能,您必须使用和unmarshaller属性设置编组器和解组器WebServiceTemplate班级。

6.1.4。使用WebServiceMessageCallback

为了适应对消息的 SOAP 标头和其他设置的设置,该WebServiceMessageCallback界面允许您在创建消息之后但在发送之前访问该消息。以下示例演示如何在通过编组对象创建的消息上设置 SOAP 操作标头:

public void marshalWithSoapActionHeader(MyObject o) {

    webServiceTemplate.marshalSendAndReceive(o, new WebServiceMessageCallback() {

        public void doWithMessage(WebServiceMessage message) {
            ((SoapMessage)message).setSoapAction("http://tempuri.org/Action");
        }
    });
}
请注意,您还可以使用org.springframework.ws.soap.client.core.SoapActionCallback设置 SOAP 操作标头。
WS-寻址

除了服务器端的 WS-Addressing支持之外,Spring Web Services 在客户端也支持这个规范。

要在客户端上设置 WS-Addressing 标头,您可以使用org.springframework.ws.soap.addressing.client.ActionCallback. 此回调将所需的操作标头作为参数。它还具有用于指定 WS-Addressing 版本和To标头的构造函数。如果未指定,则To标头默认为正在建立的连接的 URL。

以下示例将Action标头设置为http://samples/RequestOrder

webServiceTemplate.marshalSendAndReceive(o, new ActionCallback("http://samples/RequestOrder"));

6.1.5。使用WebServiceMessageExtractor

WebServiceMessageExtractor接口是一个低级回调接口,您可以完全控制Object从接收到的WebServiceMessage. 当与服务资源的底层连接仍然打开时,WebServiceTemplate调用extractData(..)提供的方法。WebServiceMessageExtractor以下示例显示了WebServiceMessageExtractor实际操作:

public void marshalWithSoapActionHeader(final Source s) {
    final Transformer transformer = transformerFactory.newTransformer();
    webServiceTemplate.sendAndReceive(new WebServiceMessageCallback() {
        public void doWithMessage(WebServiceMessage message) {
            transformer.transform(s, message.getPayloadResult());
        },
        new WebServiceMessageExtractor() {
            public Object extractData(WebServiceMessage message) throws IOException {
                // do your own transforms with message.getPayloadResult()
                //     or message.getPayloadSource()
            }
          }
        });
}

6.2. 客户端测试

在测试您的 Web 服务客户端(即使用WebServiceTemplate来访问 Web 服务的类)时,您有两种可能的方法:

  • 编写单元测试,模拟WebServiceTemplate类、WebServiceOperations接口或完整的客户端类。

    这种方法的优点是很容易实现。缺点是您并没有真正测试通过网络发送的 XML 消息的确切内容,尤其是在模拟整个客户端类时。

  • 编写集成测试,它会测试消息的内容。

第一种方法可以通过模拟框架轻松完成,例如 EasyMock、JMock 等。下一节重点介绍使用 Spring Web Services 2.0 中引入的测试功能编写集成测试。

6.2.1. 编写客户端集成测试

Spring Web Services 2.0 引入了对创建 Web 服务客户端集成测试的支持。在这种情况下,客户端是一个使用WebServiceTemplate来访问 Web 服务的类。

集成测试支持存在于org.springframework.ws.test.client包中。该包中的核心类是MockWebServiceServer. 基本思想是 Web 服务模板连接到该模拟服务器并向其发送请求消息,然后模拟服务器根据注册的期望验证该请求消息。如果满足预期,模拟服务器然后准备响应消息,将其发送回模板。

的典型用法MockWebServiceServer是: .

  1. MockWebServiceServer通过调用MockWebServiceServer.createServer(WebServiceTemplate)MockWebServiceServer.createServer(WebServiceGatewaySupport)或来创建实例MockWebServiceServer.createServer(ApplicationContext)

  2. 通过调用设置请求期望expect(RequestMatcher),可能使用RequestMatcher提供的默认实现RequestMatchers(可以静态导入)。andExpect(RequestMatcher)可以通过链接调用来设置多个期望。

  3. 通过调用创建适当的响应消息andRespond(ResponseCreator),可能使用ResponseCreator提供的默认实现ResponseCreators(可以静态导入)。

  4. WebServiceTemplate照常使用,直接或通过客户端代码。

  5. 打电话MockWebServiceServer.verify()以确保所有期望都得到满足。

请注意,MockWebServiceServer(和相关的类)提供了一个“流利的”API,因此您通常可以使用 IDE 中的代码完成功能来指导您完成设置模拟服务器的过程。
另请注意,您可以在单元测试中依赖 Spring Web Services 中可用的标准日志记录功能。有时,检查请求或响应消息以找出特定测试失败的原因可能很有用。有关详细信息,请参阅消息记录和跟踪

例如,考虑以下 Web 服务客户端类:

import org.springframework.ws.client.core.support.WebServiceGatewaySupport;

public class CustomerClient extends WebServiceGatewaySupport {                          (1)

  public int getCustomerCount() {
    CustomerCountRequest request = new CustomerCountRequest();                          (2)
    request.setCustomerName("John Doe");

    CustomerCountResponse response =
      (CustomerCountResponse) getWebServiceTemplate().marshalSendAndReceive(request);   (3)

    return response.getCustomerCount();
  }

}
1 CustomerClientextends WebServiceGatewaySupport,它为它提供了一个属性webServiceTemplate
2 CustomerCountRequest是由编组器支持的对象。例如,它可以具有@XmlRootElementJAXB2 支持的注释。
3 使用提供的CustomerClient将请求对象编组为 SOAP 消息并将其发送到 Web 服务。响应对象被解组为.WebServiceTemplateWebServiceGatewaySupportCustomerCountResponse

以下示例显示了 的典型测试CustomerClient

import javax.xml.transform.Source;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.xml.transform.StringSource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;

import org.springframework.ws.test.client.MockWebServiceServer;                         (1)
import static org.springframework.ws.test.client.RequestMatchers.*;                     (1)
import static org.springframework.ws.test.client.ResponseCreators.*;                    (1)

@RunWith(SpringJUnit4ClassRunner.class)                                                 (2)
@ContextConfiguration("integration-test.xml")                                           (2)
public class CustomerClientIntegrationTest {

  @Autowired
  private CustomerClient client;                                                        (3)

  private MockWebServiceServer mockServer;                                              (4)

  @Before
  public void createServer() throws Exception {
    mockServer = MockWebServiceServer.createServer(client);
  }

  @Test
  public void customerClient() throws Exception {
    Source requestPayload = new StringSource(
      "<customerCountRequest xmlns='http://springframework.org/spring-ws'>" +
        "<customerName>John Doe</customerName>" +
      "</customerCountRequest>");
    Source responsePayload = new StringSource(
      "<customerCountResponse xmlns='http://springframework.org/spring-ws'>" +
        "<customerCount>10</customerCount>" +
      "</customerCountResponse>");

    mockServer.expect(payload(requestPayload)).andRespond(withPayload(responsePayload));(5)

    int result = client.getCustomerCount();                                             (6)
    assertEquals(10, result);                                                           (6)

    mockServer.verify();                                                                (7)
  }

}
1 CustomerClientIntegrationTest导入和MockWebServiceServer静态导入RequestMatchersResponseCreators
2 此测试使用 Spring Framework 中提供的标准测试工具。这不是必需的,但通常是设置测试的最简单方法。
3 使用. _ CustomerClient_integration-test.xml@Autowired
4 在一个方法中,我们使用工厂方法@Before创建一个。MockWebServiceServercreateServer
5 我们通过调用静态导入提供的expect()a来定义期望(请参阅使用and)。 payload() RequestMatcherRequestMatchersRequestMatcherRequestMatchers

我们还通过调用静态导入提供的andRespond()a来设置响应(请参阅使用and)。withPayload() ResponseCreatorResponseCreatorsResponseCreatorResponseCreators

这部分测试可能看起来有点混乱,但 IDE 的代码完成功能很有帮助。键入 后expect(,您的 IDE 可以为您提供可能的请求匹配策略列表,前提是您静态导入RequestMatchers. andRespond(如果您是静态导入的,这同样适用于ResponseCreators.

6 我们调用getCustomerCount()CustomerClient因此使用WebServiceTemplate。模板现在已经设置为“测试模式”,所以这个方法调用没有建立真正的(HTTP)连接。我们还根据方法调用的结果做出一些 JUnit 断言。
7 我们调用verify()MockWebServiceServer验证是否确实收到了预期的消息。

6.2.2. 使用RequestMatcherRequestMatchers

为了验证请求消息是否满足某些期望,MockWebServiceServer使用RequestMatcher策略接口。该接口定义的合约如下:

public interface RequestMatcher {

  void match(URI uri,
             WebServiceMessage request)
    throws IOException,
           AssertionError;
}

您可以编写自己的此接口实现,AssertionError当消息不符合您的期望时抛出异常,但您当然不必这样做。该类RequestMatchers提供标准RequestMatcher实现供您在测试中使用。您通常静态导入此类。

该类RequestMatchers提供以下请求匹配器:

RequestMatchers方法 描述

anything()

期待任何类型的请求。

payload()

期望给定的请求有效负载。

validPayload()

期望请求有效负载针对给定的 XSD 架构进行验证。

xpath()

期望给定的 XPath 表达式存在、不存在或计算为给定值。

soapHeader()

期望给定的 SOAP 标头存在于请求消息中。

connectionTo()

期望与给定 URL 的连接。

andExpect()您可以通过链接调用来设置多个请求预期:

mockServer.expect(connectionTo("http://example.com")).
 andExpect(payload(expectedRequestPayload)).
 andExpect(validPayload(schemaResource)).
 andRespond(...);

有关由 提供的请求匹配器的更多信息RequestMatchers,请参阅Javadoc

6.2.3. 使用ResponseCreatorResponseCreators

当请求消息已被验证并满足定义的期望时,MockWebServiceServer创建响应消息以供WebServiceTemplate使用。服务器ResponseCreator为此使用策略接口:

public interface ResponseCreator {

  WebServiceMessage createResponse(URI uri,
                                   WebServiceMessage request,
                                   WebServiceMessageFactory messageFactory)
    throws IOException;

}

同样,您可以编写自己的此接口实现,使用消息工厂创建响应消息,但您当然不必这样做,因为ResponseCreators该类提供了标准ResponseCreator实现供您在测试中使用。您通常静态导入此类。

该类ResponseCreators提供以下响应:

ResponseCreators方法 描述

withPayload()

创建具有给定有效负载的响应消息。

withError()

在响应连接中创建错误。此方法使您有机会测试您的错误处理。

withException()

从响应连接读取时引发异常。此方法使您有机会测试您的异常处理。

withMustUnderstandFault(), withClientOrSenderFault(), withServerOrReceiverFault(), 或withVersionMismatchFault()

创建具有给定 SOAP 错误的响应消息。此方法使您有机会测试您的故障处理。

有关由 提供的请求匹配器的更多信息RequestMatchers,请参阅Javadoc

7. 使用 Spring-WS 保护您的 Web 服务

本章说明如何将 WS-Security 方面添加到您的 Web 服务中。我们专注于 WS-Security 的三个不同领域:

  • 身份验证:这是确定委托人是否是他们声称的身份的过程。在这种情况下,“主体”通常是指可以在您的应用程序中执行操作的用户、设备或其他一些系统。

  • 数字签名:消息的数字签名是基于文档和签名者私钥的一条信息。它是通过使用散列函数和私有签名函数(使用签名者的私钥加密)创建的。

  • 加密和解密:加密是将数据转换为没有适当密钥就无法读取的形式的过程。它主要用于将信息隐藏起来,不让任何人知道。解密是加密的逆过程。这是将加密数据转换回可读形式的过程。

这三个区域是通过使用XwsSecurityInterceptoror来实现的,我们分别Wss4jSecurityInterceptorXwsSecurityInterceptorUsing中进行了描述Wss4jSecurityInterceptor

请注意,WS-Security(尤其是加密和签名)需要大量内存并且会降低性能。如果性能对您很重要,您可能要考虑不使用 WS-Security 或使用基于 HTTP 的安全性。

7.1。XwsSecurityInterceptor

XwsSecurityInterceptor是一个基于 SUN 的 XML 和 Web 服务安全包 (XWSS)的EndpointInterceptor(请参阅拦截请求 -EndpointInterceptor接口)。此 WS-Security 实现是 Java Web Services Developer Pack ( Java WSDP ) 的一部分。

与任何其他端点拦截器一样,它在端点映射中定义(请参阅端点映射)。这意味着您可以选择性地添加 WS-Security 支持。一些端点映射需要它,而另一些则不需要。

请注意,XWSS 需要 SUN 1.5 JDK 和 SUN SAAJ 参考实现。WSS4J 拦截器没有这些要求(请参阅使用Wss4jSecurityInterceptor)。

需要一个XwsSecurityInterceptor安全策略文件才能运行。这个 XML 文件告诉拦截器需要从传入 SOAP 消息中获得哪些安全方面以及要向传出消息添加哪些方面。策略文件的基本格式将在以下部分进行说明,但您可以在此处找到更深入的教程。您可以使用policyConfiguration需要 Spring 资源的属性设置策略。策略文件可以包含多个元素——例如,要求传入消息的用户名令牌并签署所有传出消息。它包含一个SecurityConfiguration元素(不是JAXRPCSecurity元素)作为其根。

此外,安全拦截器需要一个或多个CallbackHandler实例才能运行。这些处理程序用于检索证书、私钥、验证用户凭据等。Spring-WS 为最常见的安全问题提供了处理程序——例如,针对 Spring Security 身份验证管理器进行身份验证和基于 X509 证书签署传出消息。以下部分说明了针对哪个安全问题使用哪个回调处理程序。您可以使用callbackHandlerorcallbackHandlers属性设置回调处理程序。

以下示例显示了如何连接XwsSecurityInterceptor

<beans>
    <bean id="wsSecurityInterceptor"
        class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
        <property name="policyConfiguration" value="classpath:securityPolicy.xml"/>
        <property name="callbackHandlers">
            <list>
                <ref bean="certificateHandler"/>
                <ref bean="authenticationHandler"/>
            </list>
        </property>
    </bean>
    ...
</beans>

这个拦截器是通过使用securityPolicy.xml类路径上的文件来配置的。它使用两个稍后在文件中定义的回调处理程序。

7.1.1. 密钥库

对于大多数加密操作,您可以使用标准java.security.KeyStore对象。这些操作包括证书验证、消息签名、签名验证和加密。它们不包括用户名和时间戳验证。本节旨在为您提供一些有关密钥库的背景知识以及可用于将密钥和证书存储在密钥库文件中的 Java 工具。这些信息大多与 Spring-WS 无关,但与 Java 的一般加密特性有关。

该类java.security.KeyStore表示加密密钥和证书的存储设施。它可以包含三种不同类型的元素:

  • 私钥:这些密钥用于自我认证。私钥伴随着对应公钥的证书链。在 WS-Security 领域内,这说明了消息签名和消息解密。

  • 对称密钥:对称(或秘密)密钥也用于消息加密和解密——不同之处在于双方(发送方和接收方)共享相同的密钥。

  • 受信任的证书:这些 X509 证书被称为“受信任的证书”,因为密钥库所有者相信证书中的公钥确实属于证书的所有者。在 WS-Security 中,这些证书用于证书验证、签名验证和加密。

使用keytool

keytool程序是一个密钥和证书管理实用程序,随您的 Java 虚拟机一起提供。您可以使用此工具创建新的密钥库、向其中添加新的私钥和证书,等等。提供该命令的完整参考超出了本文档的范围,但您可以在此处或通过在命令行上使用该命令keytool找到参考。keytool -help

使用KeyStoreFactoryBean

要使用 Spring 配置轻松加载密钥库,您可以使用KeyStoreFactoryBean. 它有一个资源位置属性,您可以将其设置为指向要加载的密钥库的路径。可以提供密码来检查密钥库数据的完整性。如果没有给出密码,则不执行完整性检查。以下清单配置了一个KeyStoreFactoryBean

<bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
    <property name="password" value="password"/>
    <property name="location" value="classpath:org/springframework/ws/soap/security/xwss/test-keystore.jks"/>
</bean>
如果您不指定 location 属性,则会创建一个新的空密钥库,这很可能不是您想要的。
KeyStoreCallbackHandler

要在 a 中使用密钥库XwsSecurityInterceptor,您需要定义一个KeyStoreCallbackHandler. 此回调具有三个类型为keystore: ( keyStoretrustStoresymmetricStore) 的属性。处理程序使用的确切存储取决于此处理程序要执行的加密操作。对于私钥操作,keyStore使用 。对于对称密钥操作,symmetricStore使用 。为了确定信任关系,trustStore使用 。下表表明了这一点:

密码操作 使用的密钥库

证书验证

首先keyStore,然后trustStore

基于私钥解密

keyStore

基于对称密钥的解密

symmetricStore

基于公钥证书的加密

trustStore

基于对称密钥的加密

symmetricStore

签约

keyStore

签名验证

trustStore

此外,它KeyStoreCallbackHandler还有一个privateKeyPassword属性,应该设置它来解锁包含在“keyStore”中的私钥。

如果symmetricStore未设置,则默认为keyStore。如果未设置密钥或信任库,则回调处理程序使用标准 Java 机制来加载或创建它。请参阅 JavaDocKeyStoreCallbackHandler以了解此机制的工作原理。

例如,如果您想使用KeyStoreCallbackHandler来验证传入的证书或签名,您可以使用信任库:

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="trustStore" ref="trustStore"/>
    </bean>

    <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:truststore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

如果你想用它来解密传入的证书或签署传出的消息,你可以使用密钥库:

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="keyStore" ref="keyStore"/>
        <property name="privateKeyPassword" value="changeit"/>
    </bean>

    <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:keystore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

以下部分说明了KeyStoreCallbackHandler可以在何处使用以及为特定加密操作设置哪些属性。

7.1.2. 验证

正如本章介绍中所述,身份验证是确定委托人是否是他们声称的身份的任务。在 WS-Security 中,身份验证可以采用两种形式:使用用户名和密码令牌(使用纯文本密码或密码摘要)或使用 X509 证书。

纯文本用户名认证

最简单的用户名身份验证形式使用纯文本密码。在这种情况下,SOAP 消息包含一个UsernameToken元素,该元素本身包含一个Username元素和一个Password包含纯文本密码的元素。可以将纯文本身份验证与 HTTP 服务器提供的基本身份验证进行比较。

请注意,纯文本密码不是很安全。因此,如果您使用它们(例如,使用 HTTPS 而不是纯 HTTP),则应始终向传输层添加额外的安全措施。

要要求每条传入消息都包含UsernameToken带有纯文本密码的 a,安全策略文件应包含一个RequireUsernameToken元素,其passwordDigestRequired属性设置为false. 您可以在此处找到可能的子元素的参考。以下清单显示了如何包含RequireUsernameToken元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    ...
    <xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="false"/>
    ...
</xwss:SecurityConfiguration>

如果用户名令牌不存在,则XwsSecurityInterceptor向发送者返回一个 SOAP 错误。如果它存在,它会向已注册的处理程序触发PasswordValidationCallbacka 。PlainTextPasswordRequest在 Spring-WS 中,有三个类处理这个特定的回调。

使用SimplePasswordValidationCallbackHandler

最简单的密码验证处理程序是SimplePasswordValidationCallbackHandler. 此处理程序根据内存Properties对象验证密码,您可以使用以下users属性指定该对象:

<bean id="passwordValidationHandler"
    class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler">
    <property name="users">
        <props>
            <prop key="Bert">Ernie</prop>
        </props>
    </property>
</bean>

在这种情况下,我们只允许用户“Bert”使用密码“Ernie”登录。

使用SpringPlainTextPasswordValidationCallbackHandler

使用SpringPlainTextPasswordValidationCallbackHandlerSpring Security对用户进行身份验证。描述 Spring Security 超出了本文档的范围,但它是一个成熟的安全框架。您可以在Spring Security 参考文档中阅读有关它的更多信息。

SpringPlainTextPasswordValidationCallbackHandler需要一个AuthenticationManager操作。它使用此管理器UsernamePasswordAuthenticationToken对其创建的 a 进行身份验证。如果身份验证成功,则令牌存储在SecurityContextHolder. 您可以使用以下authenticationManager属性设置身份验证管理器:

<beans>
  <bean id="springSecurityHandler"
      class="org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler">
    <property name="authenticationManager" ref="authenticationManager"/>
  </bean>

  <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
      <property name="providers">
          <bean class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
              <property name="userDetailsService" ref="userDetailsService"/>
          </bean>
      </property>
  </bean>

  <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
  ...
</beans>
使用JaasPlainTextPasswordValidationCallbackHandler

JaasPlainTextPasswordValidationCallbackHandler基于标准的Java 身份验证和授权服务。提供对 JAAS 的完整介绍超出了本文档的范围,但提供了一个很好的教程

JaasPlainTextPasswordValidationCallbackHandler只需一个loginContextName即可操作。LoginContext它使用此名称创建一个新的 JAAS ,并处理标准 JAAS NameCallback,并PasswordCallback使用 SOAP 消息中提供的用户名和密码。这意味着此回调处理程序与LoginModule在该login()阶段触发这些回调的任何 JAAS 集成,这是标准行为。

您可以JaasPlainTextPasswordValidationCallbackHandler按如下方式连接:

<bean id="jaasValidationHandler"
    class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasPlainTextPasswordValidationCallbackHandler">
    <property name="loginContextName" value="MyLoginModule" />
</bean>

在这种情况下,回调处理程序使用LoginContext命名的MyLoginModule. 这个模块应该在您的jaas.config文件中定义,如前面提到的教程中所述

摘要用户名认证

使用密码摘要时,SOAP 消息还包含一个UsernameToken元素,该元素本身包含一个Username元素和一个Password元素。不同之处在于密码不是作为纯文本发送,而是作为摘要发送。接收者将此摘要与他根据用户的已知密码计算的摘要进行比较,如果它们相同,则对用户进行身份验证。此方法与 HTTP 服务器提供的摘要式身份验证相当。

要要求每条传入消息都包含一个UsernameToken带有密码摘要的元素,安全策略文件应该包含一个RequireUsernameToken元素,其passwordDigestRequired属性设置为true. 此外,该nonceRequired属性应设置为:您可以在此处true找到可能的子元素的引用。以下清单显示了如何定义元素:RequireUsernameToken

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    ...
    <xwss:RequireUsernameToken passwordDigestRequired="true" nonceRequired="true"/>
    ...
</xwss:SecurityConfiguration>

如果用户名令牌不存在,则XwsSecurityInterceptor向发送者返回一个 SOAP 错误。如果它存在,它会向已注册的处理程序触发PasswordValidationCallbacka 。DigestPasswordRequest在 Spring-WS 中,有两个类处理这个特定的回调:SimplePasswordValidationCallbackHandlerSpringDigestPasswordValidationCallbackHandler.

使用SimplePasswordValidationCallbackHandler

SimplePasswordValidationCallbackHandler可以处理纯文本密码和密码摘要。它在使用SimplePasswordValidationCallbackHandler中进行了描述。

使用SpringDigestPasswordValidationCallbackHandler

SpringDigestPasswordValidationCallbackHandler需要 Spring SecurityUserDetailService才能运行。它使用此服务来检索令牌中指定的用户的密码。然后将此详细信息对象中包含的密码摘要与消息中的摘要进行比较。如果它们相等,则用户已成功通过身份验证,并且 aUsernamePasswordAuthenticationToken存储在SecurityContextHolder. 您可以使用该userDetailsService属性设置服务。此外,您可以设置一个userCache属性,以缓存加载的用户详细信息。以下示例显示了如何执行此操作:

<beans>
    <bean class="org.springframework.ws.soap.security.xwss.callback.SpringDigestPasswordValidationCallbackHandler">
        <property name="userDetailsService" ref="userDetailsService"/>
    </bean>

    <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
    ...
</beans>
证书认证

更安全的身份验证方式使用 X509 证书。在这种情况下,SOAP 消息包含一个`BinarySecurityToken`,它包含一个 X509 证书的 Base 64 编码版本。接收方使用证书进行身份验证。存储在消息中的证书也用于对消息进行签名(请参阅验证签名)。

为了确保所有传入的 SOAP 消息都带有一个`BinarySecurityToken`,安全策略文件应该包含一个RequireSignature元素。该元素可以进一步携带其他元素,这些元素在验证签名中有所介绍。您可以在此处找到可能的子元素的参考。以下清单显示了如何定义RequireSignature元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    ...
    <xwss:RequireSignature requireTimestamp="false">
    ...
</xwss:SecurityConfiguration>

当没有携带证书的消息到达时,XwsSecurityInterceptor向发送者返回一个 SOAP 错误。如果它存在,它会触发一个CertificateValidationCallback. Spring-WS 中的三个处理程序处理此回调以进行身份​​验证:

在大多数情况下,证书身份验证应该先于证书验证,因为您只想针对有效证书进行身份验证。应忽略无效证书,例如过期日期已过或不在您的受信任证书存储中的证书。

在 Spring-WS 术语中,这意味着SpringCertificateValidationCallbackHandlerorJaasCertificateValidationCallbackHandler应该以KeyStoreCallbackHandler. 这可以通过callbackHandlers在配置中设置属性的顺序来完成XwsSecurityInterceptor

<bean id="wsSecurityInterceptor"
    class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
    <property name="policyConfiguration" value="classpath:securityPolicy.xml"/>
    <property name="callbackHandlers">
        <list>
            <ref bean="keyStoreHandler"/>
            <ref bean="springSecurityHandler"/>
        </list>
    </property>
</bean>

使用此设置,拦截器首先使用密钥库确定消息中的证书是否有效,然后对其进行身份验证。

使用KeyStoreCallbackHandler

使用KeyStoreCallbackHandler标准 Java 密钥库来验证证书。此证书验证过程包括以下步骤: .

  1. 处理程序检查证书是否在 private 中keyStore。如果是,它是有效的。

  2. 如果证书不在私钥库中,则处理程序检查当前日期和时间是否在证书中给定的有效期内。如果不是,则证书无效。如果是,则继续执行最后一步。

  3. 创建证书的证书路径。这基本上意味着处理程序确定证书是否已由trustStore. 如果可以成功构建证书路径,则证书有效。否则,证书无效。

要将KeyStoreCallbackHandler用于证书验证目的,您很可能只需要设置trustStore属性:

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="trustStore" ref="trustStore"/>
    </bean>

    <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:truststore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

使用前面示例中显示的设置,要验证的证书必须位于信任库本身中,或者信任库必须包含颁发证书的证书颁发机构。

使用SpringCertificateValidationCallbackHandler

SpringCertificateValidationCallbackHandler需要 Spring SecurityAuthenticationManager才能运行。它使用此管理器X509AuthenticationToken对其创建的 a 进行身份验证。配置的身份验证管理器应提供可以处理此令牌的提供程序(通常是 的实例X509AuthenticationProvider)。如果身份验证成功,则令牌存储在SecurityContextHolder. 您可以使用以下authenticationManager属性设置身份验证管理器:

<beans>
    <bean id="springSecurityCertificateHandler"
        class="org.springframework.ws.soap.security.xwss.callback.SpringCertificateValidationCallbackHandler">
        <property name="authenticationManager" ref="authenticationManager"/>
    </bean>

    <bean id="authenticationManager"
        class="org.springframework.security.providers.ProviderManager">
        <property name="providers">
            <bean class="org.springframework.ws.soap.security.x509.X509AuthenticationProvider">
                <property name="x509AuthoritiesPopulator">
                    <bean class="org.springframework.ws.soap.security.x509.populator.DaoX509AuthoritiesPopulator">
                        <property name="userDetailsService" ref="userDetailsService"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

  <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
  ...
</beans>

在这种情况下,我们使用自定义用户详细信息服务来获取基于证书的身份验证详细信息。有关针对 X509 证书进行身份验证的更多信息,请参阅Spring Security 参考文档。

使用JaasCertificateValidationCallbackHandler

JaasCertificateValidationCallbackHandler需要一个loginContextName操作。LoginContext它通过使用此名称和X500Principal证书的 来创建一个新的 JAAS 。这意味着此回调处理程序与任何LoginModule处理 X500 主体的 JAAS 集成。

您可以JaasCertificateValidationCallbackHandler按如下方式连接:

<bean id="jaasValidationHandler"
    class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasCertificateValidationCallbackHandler">
    <property name="loginContextName">MyLoginModule</property>
</bean>

在这种情况下,回调处理程序使用LoginContext命名的MyLoginModule. 该模块应该在您的jaas.config文件中定义,并且应该能够针对 X500 主体进行身份验证。

7.1.3. 数字签名

消息的数字签名是基于文档和签名者私钥的一条信息。两个主要任务与 WS-Security 中的签名相关:验证签名和签署消息。

验证签名

基于证书的身份验证一样,签名消息包含一个BinarySecurityToken,其中包含用于对消息进行签名的证书。此外,它包含一个SignedInfo块,指示消息的哪一部分已签名。

为了确保所有传入的 SOAP 消息都带有BinarySecurityToken,安全策略文件应该包含一个RequireSignature元素。它还可以包含一个SignatureTarget元素,该元素指定预期要签名的目标消息部分和各种其他子元素。您还可以定义要使用的私钥别名,是否使用对称而不是私钥,以及许多其他属性。您可以在此处找到可能的子元素的参考。以下清单配置了一个RequireSignature元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:RequireSignature requireTimestamp="false"/>
</xwss:SecurityConfiguration>

如果签名不存在,则XwsSecurityInterceptor向发送者返回一个 SOAP 错误。如果存在,它会SignatureVerificationKeyCallback向已注册的处理程序触发 a。在 Spring-WS 中,一个类处理这个特定的回调:KeyStoreCallbackHandler.

使用KeyStoreCallbackHandler

KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler使用 ajava.security.KeyStore处理各种加密回调,包括签名验证。对于签名验证,处理程序使用以下trustStore属性:

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="trustStore" ref="trustStore"/>
    </bean>

    <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:org/springframework/ws/soap/security/xwss/test-truststore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>
签署消息

签署消息时,XwsSecurityInterceptor会在BinarySecurityToken消息中添加 。它还添加了一个SignedInfo块,指示消息的哪一部分已签名。

要签署所有传出的 SOAP 消息,安全策略文件应该包含一个Sign元素。它还可以包含一个SignatureTarget元素,该元素指定预期要签名的目标消息部分和各种其他子元素。您还可以定义要使用的私钥别名,是否使用对称而不是私钥,以及许多其他属性。您可以在此处找到可能的子元素的参考。以下示例包含一个Sign元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
	<xwss:Sign includeTimestamp="false" />
</xwss:SecurityConfiguration>

向已注册的处理程序触发XwsSecurityInterceptorSignatureKeyCallback在 Spring-WS 中,KeyStoreCallbackHandler该类处理这个特定的回调。

使用KeyStoreCallbackHandler

KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler使用 ajava.security.KeyStore来处理各种加密回调,包括签名消息。为了添加签名,处理程序使用该keyStore属性。此外,您必须设置privateKeyPassword属性以解锁用于签名的私钥。以下示例使用KeyStoreCallbackHandler:

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="keyStore" ref="keyStore"/>
        <property name="privateKeyPassword" value="changeit"/>
    </bean>

    <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:keystore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

7.1.4。解密和加密

加密时,消息被转换为只能用适当的密钥读取的形式。可以解密消息以显示原始的可读消息。

解密

要解密传入的 SOAP 消息,安全策略文件应该包含一个RequireEncryption元素。该元素还可以携带一个EncryptionTarget元素,该元素指示应该对消息的哪一部分进行加密,以及一个SymmetricKey指示应该使用共享密钥而不是常规私钥来解密消息的元素。您可以在此处阅读其他元素的说明。以下示例使用一个RequireEncryption元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:RequireEncryption />
</xwss:SecurityConfiguration>

如果传入消息未加密,则XwsSecurityInterceptor向发送者返回一个 SOAP 错误。如果存在,它会DecryptionKeyCallback向已注册的处理程序触发 a。在 Spring-WS 中,KeyStoreCallbackHandler该类处理这个特定的回调。

使用KeyStoreCallbackHandler

KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler使用 ajava.security.KeyStore来处理各种加密回调,包括解密。对于解密,处理程序使用该keyStore属性。此外,您必须设置该privateKeyPassword属性以解锁用于解密的私钥。对于基于对称密钥的解密,它使用symmetricStore. 以下示例使用KeyStoreCallbackHandler

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="keyStore" ref="keyStore"/>
        <property name="privateKeyPassword" value="changeit"/>
    </bean>

    <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:keystore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>
加密

要加密传出的 SOAP 消息,安全策略文件应该包含一个Encrypt元素。该元素还可以携带一个EncryptionTarget元素,该元素指示应该对消息的哪一部分进行加密,以及一个SymmetricKey指示应该使用共享密钥而不是常规公钥来加密消息的元素。您可以在此处阅读其他元素的说明。以下示例使用一个Encrypt元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:Encrypt />
</xwss:SecurityConfiguration>

向已注册的处理程序XwsSecurityInterceptor触发EncryptionKeyCallback以检索加密信息。在 Spring-WS 中,KeyStoreCallbackHandler该类处理这个特定的回调。

使用KeyStoreCallbackHandler

KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler使用 ajava.security.KeyStore来处理各种加密回调,包括加密。对于基于公钥的加密,处理程序使用该trustStore属性。对于基于对称密钥的加密,它使用symmetricStore. 以下示例使用KeyStoreCallbackHandler

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="trustStore" ref="trustStore"/>
    </bean>

    <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:truststore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

7.1.5。安全异常处理

当安全或验证操作失败时,分别XwsSecurityInterceptor抛出 aWsSecuritySecurementExceptionWsSecurityValidationException。这些异常绕过标准异常处理机制,但由拦截器本身处理。

WsSecuritySecurementException异常handleSecurementExceptionXwsSecurityInterceptor. 默认情况下,此方法会记录错误并停止对消息的进一步处理。

同样,WsSecurityValidationException异常handleValidationExceptionXwsSecurityInterceptor. 默认情况下,此方法会创建 SOAP 1.1 客户端或 SOAP 1.2 发送方故障并将其作为响应发回。

handleSecurementException和 都是handleValidationException受保护的方法,您可以覆盖它们以更改其默认行为。

7.2. 使用Wss4jSecurityInterceptor

Wss4jSecurityInterceptor是一个基于Apache 的 WSS4JEndpointInterceptor的(请参阅拦截请求 -EndpointInterceptor接口) 。

WSS4J 实现以下标准:

  • OASIS Web 服务安全:SOAP 消息安全 1.0 标准 200401,2004 年 3 月

  • 用户名令牌配置文件V1.0

  • X.509 令牌配置文件 V1.0

此拦截器支持由AxiomSoapMessageFactory和创建的消息SaajSoapMessageFactory

7.2.1. 配置Wss4jSecurityInterceptor

WSS4J 不使用外部配置文件。拦截器完全由属性配置。此拦截器调用的验证和安全操作分别通过validationActionssecurementActions属性指定。操作作为空格分隔的字符串传递。以下清单显示了一个示例配置:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="UsernameToken Encrypt"/>
    ...
    <property name="securementActions" value="Encrypt"/>
    ...
</bean>

下表显示了可用的验证操作:

验证操作 描述

UsernameToken

验证用户名令牌

Timestamp

验证时间戳

Encrypt

解密消息

Signature

验证签名

NoSecurity

未执行任何操作

下表显示了可用的安全措施:

担保行动 描述

UsernameToken

添加用户名令牌

UsernameTokenSignature

添加用户名令牌和签名用户名令牌密钥

Timestamp

添加时间戳

Encrypt

加密响应

Signature

签署回应

NoSecurity

未执行任何操作

动作的顺序很重要,由拦截器强制执行。如果其安全操作的执行顺序与“validationActions”指定的顺序不同,则拦截器会拒绝传入的 SOAP 消息。

7.2.2. 处理数字证书

对于需要与密钥库交互或证书处理(签名、加密和解密操作)的加密操作,WSS4J 需要`org.apache.ws.security.components.crypto.Crypto` 的实例。

Crypto实例可以从 WSS4J 获得,CryptoFactory或者更方便地使用 Spring-WS`CryptoFactoryBean` 获得。

CryptoFactoryBean

Spring-WS 提供了一个方便的工厂 bean,它通过强类型属性(首选)或对象CryptoFactoryBean来构造和配置实例。CryptoProperties

默认情况下,CryptoFactoryBean返回org.apache.ws.security.components.crypto.Merlin. 您可以通过设置cryptoProvider属性(或其等效的org.apache.ws.security.crypto.provider字符串属性)来更改它。

以下示例配置使用CryptoFactoryBean

<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
    <property name="keyStorePassword" value="mypassword"/>
    <property name="keyStoreLocation" value="file:/path_to_keystore/keystore.jks"/>
</bean>

7.2.3. 验证

本节介绍如何使用Wss4jSecurityInterceptor.

验证用户名令牌

Spring-WS 提供了一组回调处理程序来与 Spring Security 集成。此外,还提供了一个简单的回调处理程序,用于使用内存对象SimplePasswordValidationCallbackHandler配置用户和密码。Properties

回调处理程序是通过validationCallbackHandlerWss4jSecurityInterceptor属性配置的。

使用SimplePasswordValidationCallbackHandler

SimplePasswordValidationCallbackHandler针对内存中的Properties对象验证纯文本和摘要用户名令牌。您可以按如下方式配置它:

<bean id="callbackHandler"
    class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler">
    <property name="users">
        <props>
            <prop key="Bert">Ernie</prop>
        </props>
    </property>
</bean>
使用SpringSecurityPasswordValidationCallbackHandler

通过SpringSecurityPasswordValidationCallbackHandler使用 Spring SecurityUserDetailService来操作验证纯文本和摘要密码。它使用此服务来检索令牌中指定的用户的密码(或密码摘要)。然后将此详细信息对象中包含的密码(或密码摘要)与消息中的摘要进行比较。如果它们相等,则用户已成功认证,并且 aUsernamePasswordAuthenticationToken存储在 SecurityContextHolder 中。您可以使用userDetailsService. 此外,您可以设置一个userCache属性来缓存加载的用户详细信息,如下所示:

<beans>
    <bean class="org.springframework.ws.soap.security.wss4j.callback.SpringDigestPasswordValidationCallbackHandler">
        <property name="userDetailsService" ref="userDetailsService"/>
    </bean>

    <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
    ...
</beans>
添加用户名令牌

将用户名令牌添加到传出消息就像添加UsernameTokensecurementActions属性Wss4jSecurityInterceptor并指定和securementUsername`securementPassword`一样简单。

可以通过设置securementPasswordType属性来设置密码类型。可能的值是PasswordText纯文本密码或PasswordDigest摘要密码,这是默认值。

以下示例生成带有摘要密码的用户名令牌:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="UsernameToken"/>
    <property name="securementUsername" value="Ernie"/>
    <property name="securementPassword" value="Bert"/>
</bean>

如果选择明文密码类型,可以通过设置属性来指示拦截器添加NonceCreated元素。securementUsernameTokenElements该值必须是一个列表,其中包含由空格分隔的所需元素的名称(区分大小写)。

以下示例使用纯文本密码、aNonce和 aCreated元素生成用户名令牌:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="UsernameToken"/>
    <property name="securementUsername" value="Ernie"/>
    <property name="securementPassword" value="Bert"/>
    <property name="securementPasswordType" value="PasswordText"/>
    <property name="securementUsernameTokenElements" value="Nonce Created"/>
</bean>
证书认证

由于证书身份验证类似于数字签名,WSS4J 将其作为签名验证和保护的一部分进行处理。具体来说,该securementSignatureKeyIdentifier属性必须设置为DirectReference以指示 WSS4J 生成BinarySecurityToken包含 X509 证书的元素并将其包含在传出消息中。证书的名称和密码分别通过securementUsernamesecurementPassword属性传递,如以下示例所示:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Signature"/>
    <property name="securementSignatureKeyIdentifier" value="DirectReference"/>
    <property name="securementUsername" value="mycert"/>
    <property name="securementPassword" value="certpass"/>
    <property name="securementSignatureCrypto">
      <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
        <property name="keyStorePassword" value="123456"/>
        <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
      </bean>
    </property>
</bean>

对于证书验证,常规签名验证适用:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="validationSignatureCrypto">
      <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
        <property name="keyStorePassword" value="123456"/>
        <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
      </bean>
    </property>
</bean>

在验证结束时,拦截器通过委托给默认的 WSS4J 实现来自动验证证书的有效性。verifyCertificateTrust如果需要,您可以通过重新定义方法来更改此行为。

有关更多详细信息,请参阅数字签名

7.2.4. 安全时间戳

本节介绍Wss4jSecurityInterceptor.

验证时间戳

要验证时间戳,请添加TimestampvalidationActions属性。您可以通过设置属性设置并指定timestampStricttrue秒为单位的服务器端生存时间(默认值:300)来覆盖 SOAP 消息的发起者指定的时间戳语义。timeToLive拦截器总是拒绝已经过期的时间戳,不管是什么值timeToLive

在以下示例中,拦截器将时间戳有效性窗口限制为 10 秒,拒绝该窗口之外的任何有效时间戳令牌:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Timestamp"/>
    <property name="timestampStrict" value="true"/>
    <property name="timeToLive" value="10"/>
</bean>
添加时间戳

添加Timestamp到该securementActions属性会在传出消息中生成时间戳标头。该timestampPrecisionInMilliseconds属性指定生成的时间戳的精度是否以毫秒为单位。默认值为true。以下清单添加了一个时间戳:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Timestamp"/>
    <property name="timestampPrecisionInMilliseconds" value="true"/>
</bean>

7.2.5。数字签名

本节介绍Wss4jSecurityInterceptor.

验证签名

要指示Wss4jSecurityInterceptorvalidationActions必须包含Signature操作。此外,该validationSignatureCrypto属性必须指向包含发起者公共证书的密钥库:

<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="validationSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
</bean>
签署消息

通过将Signature操作添加到securementActions. 要使用的私钥的别名和密码分别由securementUsernamesecurementPassword属性指定。securementSignatureCrypto必须指向包含私钥的密钥库:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Signature"/>
    <property name="securementUsername" value="mykey"/>
    <property name="securementPassword" value="123456"/>
    <property name="securementSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
</bean>

securementSignatureAlgorithm此外,您可以通过设置属性来定义签名算法。

您可以通过设置securementSignatureKeyIdentifier属性来自定义要使用的密钥标识符类型。仅对签名有效IssuerSerialDirectReference

securementSignatureParts属性控制对消息的哪一部分进行签名。此属性的值是以分号分隔的元素名称列表,用于标识要签名的元素。签名部分的一般形式是{}{namespace}Element. 请注意,第一个空括号仅用于加密部分。默认行为是对 SOAP 主体进行签名。

以下示例显示了如何echoResponse在 Spring Web Services 回显示例中对元素进行签名:

<property name="securementSignatureParts"
    value="{}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>

要指定没有命名空间的元素,请使用字符串Null(区分大小写)作为命名空间名称。

如果请求中没有其他元素的本地名称为Body,则 SOAP 名称空间标识符可以为空 ( {})。

签名确认

enableSignatureConfirmation通过设置启用签名确认true。请注意,签名确认操作跨越请求和响应。这意味着即使没有相应的安全操作,也必须将 and 设置为secureResponse(这是默认值)。以下示例将属性设置为:validateRequesttrueenableSignatureConfirmationtrue

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="enableSignatureConfirmation" value="true"/>
    <property name="validationSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="file:/keystore.jks"/>
        </bean>
    </property>
</bean>

7.2.6。解密和加密

本节介绍Wss4jSecurityInterceptor.

解密

传入 SOAP 消息的解密需要将Encrypt操作添加到validationActions属性中。其余配置取决于消息中出现的关键信息。(这是因为 WSS4J 只需要加密密钥的 Crypto,而嵌入式密钥名称验证被委托给回调处理程序。)

要使用嵌入的加密对称密钥(xenc:EncryptedKey元素)解密消息,validationDecryptionCrypto需要指向包含解密私钥的密钥库。此外,validationCallbackHandler必须注入一个org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler指定密钥密码的:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Encrypt"/>
    <property name="validationDecryptionCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
    <property name="validationCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
            <property name="privateKeyPassword" value="mykeypass"/>
        </bean>
    </property>
</bean>

要支持使用嵌入式密钥名称(ds:KeyName元素)解密消息,您可以配置一个KeyStoreCallbackHandler指向具有对称密钥的密钥库的密钥。该symmetricKeyPassword属性表示密钥的密码,密钥名称由ds:KeyName元素指定:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Encrypt"/>
    <property name="validationCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
            <property name="keyStore">
                <bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
                    <property name="location" value="classpath:keystore.jks"/>
                    <property name="type" value="JCEKS"/>
                    <property name="password" value="123456"/>
                </bean>
            </property>
            <property name="symmetricKeyPassword" value="mykeypass"/>
        </bean>
    </property>
</bean>
加密

添加EncryptsecurementActions启用传出消息的加密。securementEncryptionUser您可以通过设置属性来设置证书的别名以用于加密。securementEncryptionCrypto通过属性访问证书所在的密钥库。由于加密依赖于公共证书,因此无需传递密码。以下示例使用该securementEncryptionCrypto属性:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Encrypt"/>
    <property name="securementEncryptionUser" value="mycert"/>
    <property name="securementEncryptionCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="file:/keystore.jks"/>
        </bean>
    </property>
</bean>

您可以通过多种方式自定义加密: 要使用的密钥标识符类型由securementEncryptionKeyIdentifier属性定义。可能的值为IssuerSerialX509KeyIdentifierDirectReferenceThumbprintSKIKeyIdentifierEmbeddedKeyName

如果选择EmbeddedKeyName类型,则需要指定用于加密的密钥。键的别名在securementEncryptionUser属性中设置,与其他键标识符类型一样。但是,WSS4J 需要回调处理程序来获取密钥。因此,您必须提供指向适当密钥库securementCallbackHandler的那个。KeyStoreCallbackHandler默认情况下,ds:KeyName生成的 WS-Security 标头中的元素采用securementEncryptionUser属性的值。要指示不同的名称,您可以将 设置securementEncryptionEmbeddedKeyName为所需的值。在下一个示例中,传出消息使用密钥 aliased 加密,secretKey而出myKey现在ds:KeyName元素中:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Encrypt"/>
    <property name="securementEncryptionKeyIdentifier" value="EmbeddedKeyName"/>
    <property name="securementEncryptionUser" value="secretKey"/>
    <property name="securementEncryptionEmbeddedKeyName" value="myKey"/>
    <property name="securementCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
            <property name="symmetricKeyPassword" value="keypass"/>
            <property name="keyStore">
                <bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
                    <property name="location" value="file:/keystore.jks"/>
                    <property name="type" value="jceks"/>
                    <property name="password" value="123456"/>
                </bean>
            </property>
        </bean>
    </property>
</bean>

securementEncryptionKeyTransportAlgorithm属性定义用于加密生成的对称密钥的算法。支持的值为http://www.w3.org/2001/04/xmlenc#rsa-1_5(默认值)和http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p

您可以通过设置securementEncryptionSymAlgorithm属性来设置要使用的对称加密算法。支持的值为http://www.w3.org/2001/04/xmlenc#aes128-cbc(默认)http://www.w3.org/2001/04/xmlenc#tripledes-cbc、、、http://www.w3.org/2001/04/xmlenc#aes256-cbchttp://www.w3.org/2001/04/xmlenc#aes192-cbc

最后,该securementEncryptionParts属性定义了消息的哪些部分被加密。此属性的值是以分号分隔的元素名称列表,用于标识要加密的元素。一个加密模式说明符和一个命名空间标识,每个都在一对大括号内,可以在每个元素名称之前。加密模式说明符是{Content}{Element}请参阅 W3C XML 加密规范,了解元素加密和内容加密之间的区别。以下示例echoResponse从 echo 样本中识别出:

<property name="securementEncryptionParts"
    value="{Content}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>

请注意,元素名称、命名空间标识符和加密修饰符是区分大小写的。您可以省略加密修饰符和命名空间标识符。如果这样做,则加密模式默认为Content,并且名称空间设置为 SOAP 名称空间。

要指定没有命名空间的元素,请使用值Null(区分大小写)作为命名空间名称。如果未指定列表,则处理程序Content默认以模式加密 SOAP 主体。

7.2.7. 安全异常处理

的异常处理与 的Wss4jSecurityInterceptor相同XwsSecurityInterceptor。有关详细信息,请参阅安全异常处理

3.其他资源

除了本参考文档之外,还有许多其他资源可以帮助您了解如何使用 Spring Web 服务。本节列举了这些额外的第三方资源。

参考书目

  • [waldo-94] 吉姆·沃尔多、安·沃尔拉斯和山姆·肯德尔。关于分布式计算的说明。施普林格出版社。1994

  • [高山] Steve Loughran & Edmund Smith。重新思考 Java SOAP 堆栈。2005 年 5 月 17 日。 © 2005 IEEE Telephone Laboratories, Inc.

  • [有效企业 java] 泰德纽华德。斯科特迈耶斯。有效的企业 Java。艾迪生-韦斯利。2004年

  • [有效-xml] 艾略特·鲁斯蒂·哈罗德。斯科特迈耶斯。有效的 XML。艾迪生-韦斯利。2004年


1. see XML Configuration