© 2005-2020 原始作者。
允许为自用和分发目的复制本文档,前提是您不为此类复制收取任何费用,并且无论以印刷或电子方式分发,每份副本都包含此版权声明。 |
前言
在当前面向服务架构(SOA)的时代,越来越多的人使用 Web Services 连接以前互不关联的系统。最初,Web Services 被认为是执行远程过程调用(RPC)的另一种方式。然而随着时间的推移,人们发现 RPC 和 Web Services 之间存在很大的区别。特别是当跨平台互操作性很重要时,发送包含处理请求所需所有数据的封装 XML 文档通常是更好的选择。从概念上讲,基于 XML 的 Web Services 与消息队列相比,比远程解决方案更具可比性。总的来说,XML 应该被视为数据的平台无关表示形式,是 SOA 的通用语言。在开发或使用 Web Services 时,重点应放在 XML 上,而不是 Java 上。
Spring Web Services 专注于创建这些文档驱动的 Web Services。Spring Web Services 促进了契约优先的 SOAP 服务开发,通过使用多种处理 XML 有效负载的方式,允许创建灵活的 Web Services。Spring-WS 提供了一个强大的消息分发框架,一个与现有应用程序安全解决方案集成的 WS-Security 解决方案,以及遵循熟悉的 Spring 模板模式的客户端 API。
I. 介绍
1. 什么是 Spring Web Services?
1.1. 介绍
Spring Web Services (Spring-WS) 是 Spring 社区的一个产品,专注于创建文档驱动的 Web Services。Spring Web Services 旨在促进契约优先的 SOAP 服务开发,通过使用多种方式处理 XML 有效负载来创建灵活的 Web Services。该产品基于 Spring 本身,这意味着你可以将 Spring 概念(如依赖注入)作为 Web Service 的一个组成部分来使用。
人们使用 Spring-WS 的原因有很多,但大多数人在发现其他 SOAP 栈在遵循 Web Service 最佳实践方面有所欠缺后转向它。Spring-WS 使最佳实践变得容易实现。这包括 WS-I 基本配置文件、契约优先开发以及契约与实现之间的松散耦合等实践。Spring Web Services 的其他关键特性包括
1.1.3. 灵活的 XML 编组
Spring Web Services 构建在 Spring Framework 的对象/XML 映射模块之上,该模块支持 JAXB 1 和 2、Castor、XMLBeans、JiBX 和 XStream。
1.1.4. 重用你的 Spring 经验
Spring-WS 使用 Spring 应用程序上下文进行所有配置,这应该能帮助 Spring 开发人员快速上手。此外,Spring-WS 的架构与 Spring-MVC 的架构相似。
1.2. 运行时环境
Spring Web Services 需要标准的 Java 8 运行时环境。Spring-WS 构建在 Spring Framework 4.0.9 之上,但也支持更高版本。
Spring-WS 由多个模块组成,这些模块在本节的剩余部分进行描述。
-
XML 模块 (
spring-xml.jar
) 包含用于 Spring Web Services 的各种 XML 支持类。该模块主要用于 Spring-WS 框架本身,而非 Web Service 开发人员。 -
核心模块 (
spring-ws-core.jar
) 是 Spring Web Services 功能的核心部分。它提供了核心的WebServiceMessage
和SoapMessage
接口,服务器端框架(具有强大的消息分发能力),用于实现 Web Service 端点的各种支持类,以及客户端WebServiceTemplate
。 -
支持模块 (
spring-ws-support.jar
) 包含额外的传输(JMS、Email 等)。 -
安全包 (
spring-ws-security.jar
) 提供了一个 WS-Security 实现,该实现与核心 Web Service 包集成。它允许你对 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 (仅支持基于 XSD 生成 WSDL 1.1)
-
WS-I 基本配置文件 1.0, 1.1, 1.2, 和 2.0
-
WS-Addressing 1.0 和 2004 年 8 月草案
-
SOAP Message Security 1.1, Username Token Profile 1.1, X.509 Certificate Token Profile 1.1, SAML Token Profile 1.1, Kerberos Token Profile 1.1, Basic Security Profile 1.1
2. 为什么采用契约优先?
在创建 Web Services 时,有两种开发风格:契约后置(contract-last)和契约优先(contract-first)。使用契约后置方法时,你从 Java 代码开始,然后从代码生成 Web Service 契约(WSDL — 参见侧边栏)。使用契约优先方法时,你从 WSDL 契约开始,然后使用 Java 实现该契约。
Spring-WS 仅支持契约优先开发风格,本节将解释原因。
2.1. 对象/XML 阻抗不匹配
类似于 ORM 领域存在的对象-关系阻抗不匹配问题,将 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 Service 最重要的目标之一是实现互操作性:支持 Java、.NET、Python 等多种平台。由于所有这些语言都有不同的类库,因此你必须使用一些通用的、跨语言的格式来进行通信。这种格式就是 XML,它受到所有这些语言的支持。
由于这种转换,你必须确保在服务实现中使用可移植的类型。例如,考虑一个返回 java.util.TreeMap
的服务
public Map getFlights() {
// use a tree map, to make sure it's sorted
TreeMap map = new TreeMap();
map.put("KL1117", "Stockholm");
...
return map;
}
毫无疑问,这个 map 的内容可以转换为某种形式的 XML,但由于 XML 中没有描述 map 的标准方法,因此它将是专有的。此外,即使它可以转换为 XML,许多平台也没有类似于 TreeMap
的数据结构。因此,当 .NET 客户端访问你的 Web Service 时,它可能会得到一个语义不同的 System.Collections.Hashtable
。
当在客户端工作时,这个问题也存在。考虑以下描述服务契约的 XSD 片段
<element name="GetFlightsRequest">
<complexType>
<all>
<element name="departureDate" type="date"/>
<element name="from" type="string"/>
<element name="to" type="string"/>
</all>
</complexType>
</element>
这个契约定义了一个请求,它接受一个 date
类型,这是一个表示年、月和日的 XSD 数据类型。如果从 Java 调用这个服务,我们可能会使用 java.util.Date
或 java.util.Calendar
。然而,这两个类实际上都描述了时间,而不是日期。因此,我们实际上发送的数据表示的是 2007 年 4 月 4 日午夜 (2007-04-04T00:00:00
),这与 2007-04-04
不同。
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
}
这是一个循环图:Flight
引用 Passenger
,而 Passenger
又引用 Flight
。像这样的循环图在 Java 中很常见。如果我们天真地将其转换为 XML,我们会得到类似这样的结构
<flight number="KL1117">
<passengers>
<passenger>
<name>Arjen Poutsma</name>
<flight number="KL1117">
<passengers>
<passenger>
<name>Arjen Poutsma</name>
<flight number="KL1117">
<passengers>
<passenger>
<name>Arjen Poutsma</name>
...
处理这种结构可能会花费很长时间才能完成,因为这个循环没有停止条件。
解决这个问题的一种方法是使用对已经编组过的对象的引用
<flight number="KL1117">
<passengers>
<passenger>
<name>Arjen Poutsma</name>
<flight href="KL1117" />
</passenger>
...
</passengers>
</flight>
这解决了递归问题,但也引入了新问题。首先,你不能使用 XML 验证器来验证这种结构。另一个问题是,在 SOAP 中使用这些引用的标准方式 (RPC/encoded) 已经弃用,转而使用 document/literal (参见 WS-I Basic Profile)。
这些只是处理 O/X 映射时遇到的一些问题。在编写 Web Services 时,重要的是要尊重这些问题。最好的方法是完全专注于 XML,同时使用 Java 作为实现语言。这就是契约优先的全部意义所在。
2.2. 契约优先 vs 契约后置
除了上一节提到的对象/XML 映射问题外,还有其他原因偏好契约优先的开发风格。
2.2.1. 脆弱性
如前所述,契约后置开发风格导致 Web Service 契约(WSDL 和 XSD)从你的 Java 契约(通常是一个接口)生成。如果采用这种方法,你无法保证契约随时间保持不变。每次更改 Java 契约并重新部署时,Web Service 契约可能会随之改变。
此外,并非所有 SOAP 栈都会从 Java 契约生成相同的 Web Service 契约。这意味着出于任何原因更改当前 SOAP 栈可能会改变你的 Web Service 契约。
当 Web Service 契约发生变化时,需要告知使用该契约的用户获取新契约,并可能需要更改他们的代码以适应契约的任何变化。
一个有用的契约必须尽可能长时间保持不变。如果契约发生变化,你必须联系所有使用你的服务的用户,并告知他们获取新版本的契约。
2.2.2. 性能
当 Java 对象自动转换为 XML 时,无法确定通过网络发送了什么。一个对象可能引用另一个对象,后者又引用另一个对象,依此类推。最终,虚拟机堆中的一半对象可能被转换为 XML,从而导致响应时间变慢。
使用契约优先时,你明确地描述了将什么 XML 发送到哪里,从而确保它完全是你想要的。
3. 编写契约优先的 Web Services
本教程将向你展示如何编写契约优先的 Web Services — 也就是说,如何先从 XML Schema 或 WSDL 契约开始,然后编写 Java 代码。Spring-WS 专注于这种开发风格,本教程应该能帮助你入门。请注意,本教程的第一部分几乎不包含 Spring-WS 特定的信息。它主要关于 XML、XSD 和 WSDL。第二部分着重于使用 Spring-WS 实现这个契约。
进行契约优先 Web Service 开发时,最重要的事情是以 XML 的方式思考。这意味着 Java 语言的概念相对次要。通过网络发送的是 XML,你应该专注于此。使用 Java 实现 Web Service 只是一个实现细节。
在本教程中,我们将定义一个由人力资源部门创建的 Web Service。客户端可以向此服务发送假期请求表来预订假期。
3.1. 消息
在本节中,我们将重点介绍发送到 Web Service 和从 Web Service 发送的实际 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. 假期请求
holiday
元素和 employee
元素都可以放在 <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 Services。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 Service 应该能够接受所有这些元素作为数据。这是不希望的:我们只想接受一个 <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
类型,我们可以使用它。我们还将 NCName
s 更改为 string
实例。最后,我们将 <HolidayRequest/>
中的 sequence
更改为 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 | 我们对 <StartDate/> 和 <EndDate/> 使用 xs:date 数据类型(它包含年、月和日)。 |
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>
我们将消息添加到一个端口类型(port type)作为操作(operation)
<wsdl:portType name="HumanResource">
<wsdl:operation name="Holiday">
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>
</wsdl:operation>
</wsdl:portType>
该消息完成了 WSDL 的抽象部分(可以理解为接口),然后是具体部分。具体部分包括一个 binding
(它告诉客户端如何调用你刚刚定义的操作)和一个 service
(它告诉客户端在哪里调用它)。
添加具体部分非常标准。为此,请引用你之前定义的抽象部分,确保对 soap:binding
元素使用 document/literal
(rpc/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 | 我们导入数据契约中定义的模式。 |
2 | 我们定义 HolidayRequest 消息,它将在 portType 中使用。 |
3 | HolidayRequest 类型在模式中定义。 |
4 | 我们定义 HumanResource 端口类型,它将在 binding 中使用。 |
5 | 我们定义 HumanResourceBinding 绑定,它将在 port 中使用。 |
6 | 我们使用 document/literal 风格。 |
7 | 文本 http://schemas.xmlsoap.org/soap/http 表示 HTTP 传输。 |
8 | soapAction 属性表示将随每个请求发送的 SOAPAction HTTP 头部。 |
9 | http://localhost:8080/holidayService/ 地址是 Web Service 可以被调用的 URL。 |
前面的清单显示了最终的 WSDL。我们将在下一节描述如何实现由此产生的模式和 WSDL。
3.4. 创建项目
在本节中,我们使用 Maven 为我们创建初始项目结构。这样做不是强制要求,但能极大地减少我们为搭建 HolidayService 所需编写的代码量。
以下命令通过使用 Spring-WS 原型(即项目模板)为我们创建一个 Maven Web 应用程序项目
mvn archetype:create -DarchetypeGroupId=org.springframework.ws \ -DarchetypeArtifactId=spring-ws-archetype \ -DarchetypeVersion= \ -DgroupId=com.mycompany.hr \ -DartifactId=holidayService
上述命令会创建一个名为 holidayService
的新目录。在该目录下有一个 src/main/webapp
目录,其中包含 WAR 文件的根。你可以在这里找到标准的 Web 应用程序部署描述符('WEB-INF/web.xml'
),它定义了一个 Spring-WS 的 MessageDispatcherServlet
并将所有入站请求映射到此 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,例如 EndPoints
和 WebServiceMessageReceivers
,并用于创建一个新的 Spring 容器。该文件的名称来源于其对应的 servlet 的名称(在本例中是 'spring-ws'
),并在其后附加了 -servlet.xml
。因此,如果你定义了一个名为 'dynamite'
的 MessageDispatcherServlet
,则 Spring-WS 特定的配置文件的名称将变为 WEB-INF/dynamite-servlet.xml
。
(你可以在 [tutorial.example.sws-conf-file] 中查看此示例的 WEB-INF/spring-ws-servlet.xml
文件内容。)
一旦创建了项目结构,你就可以将上一节中的 schema 和 WSDL 放入 'WEB-INF/'
文件夹中。
3.5. 实现 Endpoint
在 Spring-WS 中,你实现 endpoint 来处理入站 XML 消息。endpoint 通常通过使用 @Endpoint
注解来注解一个类来创建。在这个 endpoint 类中,你可以创建一个或多个方法来处理入站请求。方法的签名可以非常灵活。你可以包含几乎任何与入站 XML 消息相关的参数类型,我们将在本章后面进行解释。
3.5.1. 处理 XML 消息
以下列表显示了定义我们 holiday endpoint 的类
package com.mycompany.hr.ws;
@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 表达式。有四个表达式:用于提取 <StartDate> 文本值的 //hr:StartDate ,用于提取结束日期的 //hr:EndDate ,以及两个用于提取员工姓名的表达式。 |
3 | @PayloadRoot 注解告诉 Spring-WS,handleHolidayRequest 方法适合处理 XML 消息。此方法可以处理的消息类型由注解值指定。在本例中,它可以处理具有 HolidayRequest local part 和命名空间 http://mycompany.com/hr/schemas 的 XML 元素。有关将消息映射到 endpoint 的更多信息,请参见下一节。 |
4 | handleHolidayRequest(..) 方法是主要的处理方法,它接收入站 XML 消息中的 <HolidayRequest/> 元素。@RequestPayload 注解表示 holidayRequest 参数应映射到请求消息的 payload。我们使用 XPath 表达式从 XML 消息中提取字符串值,并使用 SimpleDateFormat (parseData 方法)将这些值转换为 Date 对象。有了这些值,我们调用业务服务上的一个方法。通常,这将启动一个数据库事务并更改数据库中的一些记录。最后,我们定义了一个 void 返回类型,这表明我们不想向 Spring-WS 发送响应消息。如果需要响应消息,我们可以返回一个 JDOM Element 来表示响应消息的 payload。 |
使用 JDOM 只是处理 XML 的一种选择。其他选项包括 DOM、dom4j、XOM、SAX 和 StAX,以及编组技术(如 JAXB、Castor、XMLBeans、JiBX 和 XStream),如 下一章 所述。我们选择 JDOM 是因为它允许我们访问原始 XML,并且它是基于类的(不像 W3C DOM 和 dom4j 那样基于接口和工厂方法),这使得代码更简洁。我们使用 XPath 是因为它比编组技术更不容易出错。只要能找到日期和姓名,就不需要严格遵守 schema。
由于我们使用 JDOM,我们必须在项目的根目录下的 Maven pom.xml
中添加一些依赖。以下是 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 配置文件中使用组件扫描配置这些类的方式。我们还通过 <sws:annotation-driven>
元素指示 Spring-WS 使用注解驱动的 endpoint。
<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. 将消息路由到 Endpoint
作为编写 endpoint 的一部分,我们还使用了 @PayloadRoot
注解来指示 handleHolidayRequest
方法可以处理哪种消息。在 Spring-WS 中,这个过程是由 EndpointMapping
负责的。在这里,我们通过使用 PayloadRootAnnotationMethodEndpointMapping
根据消息内容来路由消息。以下列表显示了我们之前使用的注解
@PayloadRoot(namespace = "http://mycompany.com/hr/schemas", localPart = "HolidayRequest")
上述示例中显示的注解基本意味着,每当接收到具有命名空间 http://mycompany.com/hr/schemas
和 local name HolidayRequest
的 XML 消息时,它将被路由到 handleHolidayRequest
方法。通过在我们的配置中使用 <sws:annotation-driven>
元素,我们启用对 @PayloadRoot
注解的检测。在一个 endpoint 中拥有多个相关的处理方法是可能且很常见的,每个方法处理不同的 XML 消息。
还有其他方法可以将 endpoint 映射到 XML 消息,这将在 下一章 中描述。
3.5.3. 提供 Service 和 Stub 实现
现在我们有了 endpoint,我们需要 HumanResourceService
及其实现供 HolidayEndpoint
使用。以下列表显示了 HumanResourceService
接口
package com.mycompany.hr.service;
public interface HumanResourceService {
void bookHoliday(Date startDate, Date endDate, String name);
}
出于教程目的,我们使用 HumanResourceService
的一个简单 stub 实现
package com.mycompany.hr.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 进行注解。这标志着该类是一个业务外观,使其成为 HolidayEndpoint 中通过 @Autowired 注入的候选对象。 |
3.6. 发布 WSDL
最后,我们需要发布 WSDL。如 Service Contract 中所述,我们不需要自己编写 WSDL。Spring-WS 可以根据一些约定生成一个 WSDL。以下是我们定义生成的方式
<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。在本例中,id 是 holiday ,这意味着可以在 servlet 上下文中以 holiday.wsdl 的形式检索 WSDL。完整的 URL 是 http://localhost:8080/holidayService/holiday.wsdl 。 |
2 | 接下来,我们将 WSDL 的 port type 设置为 HumanResource 。 |
3 | 我们设置了服务可达到的位置:/holidayService/ 。我们使用相对 URI,并指示框架将其动态转换为绝对 URI。因此,如果服务部署到不同的上下文,我们无需手动更改 URI。更多信息,请参见 “自动 WSDL 暴露” 一节。为了使位置转换工作,我们需要在 web.xml 中的 spring-ws servlet 中添加一个 init 参数(如下一个列表所示)。 |
4 | 我们定义了 WSDL 定义本身的目标命名空间。设置此属性不是必需的。如果未设置,WSDL 将具有与 XSD schema 相同的命名空间。 |
5 | xsd 元素引用了我们在 Data Contract 中定义的人力资源 schema。我们将 schema 放在应用程序的 WEB-INF 目录中。 |
以下列表显示了如何添加 init 参数
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
你可以使用 mvn install
创建一个 WAR 文件。如果将应用程序部署(到 Tomcat、Jetty 等)并将浏览器指向 此位置,你将看到生成的 WSDL。此 WSDL 可供客户端使用,例如 soapUI 或其他 SOAP 框架。
本教程到此结束。教程代码可以在 Spring-WS 的完整发行版中找到。如果你希望继续学习,请查看发行版中的 echo 示例应用。之后,查看 airline 示例,它稍微复杂一些,因为它使用了 JAXB、WS-Security、Hibernate 和事务性服务层。最后,你可以阅读参考文档的其余部分。
II. 参考
4. 共享组件
本章探讨了在 Spring-WS 客户端和服务器端开发之间共享的组件。这些接口和类代表了 Spring-WS 的构建块,因此你需要了解它们的作用,即使你不直接使用它们。
4.1. Web Service 消息
本节描述了 Spring-WS 使用的消息和消息工厂。
4.1.1. WebServiceMessage
Spring Web Services 的核心接口之一是 WebServiceMessage
。此接口代表与协议无关的 XML 消息。该接口包含提供对消息 payload 访问的方法,形式为 javax.xml.transform.Source
或 javax.xml.transform.Result
。Source
和 Result
是标记接口,代表对 XML 输入和输出的抽象。具体实现包装了各种 XML 表示,如下表所示
Source 或 Result 实现 | 包装的 XML 表示 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
除了从 payload 读取和向 payload 写入之外,Web 服务消息还可以将自身写入输出流。
4.1.2. SoapMessage
SoapMessage
是 WebServiceMessage
的一个子类。它包含 SOAP 特定的方法,例如获取 SOAP Headers、SOAP Faults 等等。通常,你的代码不应该依赖于 SoapMessage
,因为可以使用 WebServiceMessage
中的 getPayloadSource()
和 getPayloadResult()
来获取 SOAP Body 的内容(消息的 payload)。只有在需要执行 SOAP 特定操作(例如添加 header、获取附件等)时,才需要将 WebServiceMessage
转换为 SoapMessage
。
4.1.3. 消息工厂
具体的消息实现由 WebServiceMessageFactory
创建。此工厂可以创建一个空消息或从输入流读取消息。WebServiceMessageFactory
有两个具体的实现。一个基于 SAAJ,即 Java 的 SOAP with Attachments API。另一个基于 Axis 2 的 AXIOM (AXis Object Model)。
SaajSoapMessageFactory
SaajSoapMessageFactory
使用 Java 的 SOAP with Attachments API (SAAJ) 来创建 SoapMessage
实现。SAAJ 是 J2EE 1.4 的一部分,因此它应该在大多数现代应用服务器上都受支持。以下是常见应用服务器提供的 SAAJ 版本概述
应用服务器 | SAAJ 版本 |
---|---|
BEA WebLogic 8 |
1.1 |
BEA WebLogic 9 |
1.1/1.21 |
IBM WebSphere 6 |
1.2 |
SUN Glassfish 1 |
1.3 |
1Weblogic 9 在 SAAJ 1.2 实现中有一个已知的 bug:它实现了所有的 1.2 接口,但在调用时会抛出 |
此外,Java SE 6 包括 SAAJ 1.3。你可以如下配置 SaajSoapMessageFactory
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
SAAJ 基于 DOM,即 Document Object Model。这意味着所有 SOAP 消息都存储在内存中。对于较大的 SOAP 消息,这可能不够高效。在这种情况下,AxiomSoapMessageFactory 可能更适用。 |
AxiomSoapMessageFactory
AxiomSoapMessageFactory
使用 AXis 2 Object Model (AXIOM) 来创建 SoapMessage
实现。AXIOM 基于 StAX,即 Streaming API for XML。StAX 提供了一种基于拉的机制来读取 XML 消息,这对于较大的消息更有效率。
为了提高 AxiomSoapMessageFactory
的读取性能,你可以将 payloadCaching
属性设置为 false(默认值为 true)。这样做会导致 SOAP body 的内容直接从 socket 流中读取。启用此设置后,payload 只能读取一次。这意味着你必须确保消息的任何预处理(日志记录或其他工作)不会消耗它。
你可以如下使用 AxiomSoapMessageFactory
<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory">
<property name="payloadCaching" value="true"/>
</bean>
除了 payload 缓存之外,AXIOM 还支持完整的流式消息,如 StreamingWebServiceMessage
中定义的那样。这意味着你可以直接在响应消息上设置 payload,而不是将其写入 DOM 树或缓冲区。
当 handler 方法返回一个 JAXB2 支持的对象时,将使用 AXIOM 的完整流处理。它会自动将此已编组的对象设置到响应消息中,并在响应输出时将其写入到传出的 socket 流中。
有关完整流处理的更多信息,请参阅 StreamingWebServiceMessage
和 StreamingPayload
的类级别 Javadoc。
SOAP 1.1 或 1.2
SaajSoapMessageFactory
和 AxiomSoapMessageFactory
都有一个 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>
在上述示例中,我们定义了一个只接受 SOAP 1.2 消息的 SaajSoapMessageFactory
。
尽管 SOAP 的两个版本在格式上非常相似,但 1.2 版本与 1.1 不向后兼容,因为它使用了不同的 XML 命名空间。SOAP 1.1 和 1.2 之间的其他主要区别包括 fault 的结构不同,以及 关于 SOAP 版本号(或通常的 WS-* 规范版本号)需要注意的重要一点是,规范的最新版本通常不是最流行的版本。对于 SOAP 来说,这意味着(目前)最好使用的版本是 1.1。版本 1.2 未来可能会变得更受欢迎,但目前 1.1 是最安全的选择。 |
4.1.4. MessageContext
通常,消息成对出现:一个请求和一个响应。请求在客户端创建,通过某种传输发送到服务器端,服务器端生成响应。此响应发送回客户端,并在客户端读取。在 Spring Web Services 中,这种对话包含在 MessageContext
中,它具有获取请求和响应消息的属性。在客户端,消息上下文由 WebServiceTemplate
创建。在服务器端,消息上下文从特定于传输的输入流中读取。例如,在 HTTP 中,它从 HttpServletRequest
中读取,响应写回 HttpServletResponse
。
SOAP 协议的一个关键特性是它试图与传输无关。这就是为什么 Spring-WS 不支持通过 HTTP 请求 URL 而是通过消息内容将消息映射到 endpoint 的原因。
然而,有时需要在客户端或服务器端访问底层传输。为此,Spring Web Services 提供了 TransportContext
。传输上下文允许访问底层的 WebServiceConnection
,在服务器端通常是 HttpServletConnection
,在客户端通常是 HttpUrlConnection
或 CommonsHttpConnection
。例如,你可以在服务器端 endpoint 或 interceptor 中获取当前请求的 IP 地址
然而,有时需要在客户端或服务器端访问底层的传输。为此,Spring Web Services 提供了 TransportContext
。传输上下文允许访问底层的 WebServiceConnection
,这通常在服务器端是 HttpServletConnection
,在客户端是 HttpUrlConnection
或 CommonsHttpConnection
。例如,您可以在服务器端端点或拦截器中获取当前请求的 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 5 的 javax.xml.xpath.XPathExpression
接口或 Jaxen 的 XPath
类。要在应用程序上下文中构造表达式,可以使用 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>
前面的表达式不使用命名空间,但我们可以使用工厂 bean 的 namespaces
属性设置它们。该表达式可以在代码中如下使用
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
,它类似于 Spring JDBC 支持中的 RowMapper
。以下示例展示了如何使用它
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 中常用的模板模式(JdbcTemplate
、JmsTemplate
等)。以下列表显示了一个示例
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
logger 级别设置为 DEBUG
或 TRACE
。在 DEBUG
级别,仅记录 payload 的根元素。在 TRACE
级别,记录整个消息内容。如果只想记录发送的消息,请使用 org.springframework.ws.server.MessageTracing.sent
logger。类似地,可以使用 org.springframework.ws.server.MessageTracing.received
记录接收到的消息。
在客户端,存在类似的 logger:org.springframework.ws.client.MessageTracing.sent
和 org.springframework.ws.client.MessageTracing.received
。
以下是 log4j.properties
配置文件的示例,它记录客户端发送消息的完整内容,以及客户端接收消息的 payload 根元素。在服务器端,发送和接收消息的 payload 根元素都会被记录
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] Sent request [<SOAP-ENV:Envelope xmlns:SOAP-ENV="... DEBUG [server.MessageTracing.received] Received request [SaajSoapMessage {http://example.com}request] ... DEBUG [server.MessageTracing.sent] Sent response [SaajSoapMessage {http://example.com}response] ... DEBUG [client.MessageTracing.received] Received response [SaajSoapMessage {http://example.com}response] ...
5. 使用 Spring-WS 创建 Web 服务
Spring-WS 的服务器端支持围绕着一个 MessageDispatcher
设计,该调度器将入站消息分派到 endpoint,并具有可配置的 endpoint 映射、响应生成和 endpoint 拦截。Endpoint 通常使用 @Endpoint
注解进行注解,并具有一个或多个处理方法。这些方法通过检查消息的部分(通常是 payload)来处理入站 XML 请求消息,并创建某种响应。你可以使用另一个注解(通常是 @PayloadRoot
)对方法进行注解,以指示它可以处理哪种消息。
Spring-WS 的 XML 处理非常灵活。endpoint 可以从 Spring-WS 支持的大量 XML 处理库中进行选择,包括
-
DOM 系列:W3C DOM、JDOM、dom4j 和 XOM
-
SAX 或 StAX:用于更快的性能
-
XPath:从消息中提取信息
-
编组技术(JAXB、Castor、XMLBeans、JiBX 或 XStream):将 XML 转换为对象,反之亦然
5.1. MessageDispatcher
Spring-WS 的服务器端围绕一个中心类设计,该类将入站 XML 消息分派到 endpoint。Spring-WS 的 MessageDispatcher
非常灵活,允许你使用任何类型的类作为 endpoint,只要它可以在 Spring IoC 容器中配置。在某种程度上,消息调度器类似于 Spring 的 DispatcherServlet
,即 Spring Web MVC 中使用的“前端控制器”。
以下序列图显示了 MessageDispatcher
的处理和分派流程

当 MessageDispatcher
配置好并有一个请求到达该特定调度器时,MessageDispatcher
开始处理请求。以下过程描述了 MessageDispatcher
如何处理请求
-
搜索已配置的
EndpointMapping(s)
以查找合适的 endpoint。如果找到 endpoint,则调用与该 endpoint 关联的调用链(pre-processor、post-processor 和 endpoint)以创建响应。 -
为 endpoint 找到一个合适的 adapter。
MessageDispatcher
将调用委托给此 adapter 以调用 endpoint。 -
如果返回响应,则将其发送出去。如果未返回响应(例如,由于 pre-processor 或 post-processor 拦截了请求,出于安全原因等),则不发送响应。
在处理请求过程中抛出的异常会被应用程序上下文中声明的任何 endpoint 异常解析器捕获。使用这些异常解析器,你可以在抛出此类异常时定义自定义行为(例如返回 SOAP fault)。
MessageDispatcher
有几个用于设置 endpoint adapter、mapping、exception resolver 的属性。但是,设置这些属性不是必需的,因为调度器会自动检测应用程序上下文中注册的所有类型。只有在需要覆盖检测时,才应设置这些属性。
消息调度器在消息上下文上操作,而不是在特定于传输的输入流和输出流上操作。因此,需要将特定于传输的请求读入 MessageContext
。对于 HTTP,这是通过 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,MessageDispatcherServlet
在 Web 应用程序的 web.xml
中进行配置。你希望由 MessageDispatcherServlet
处理的请求必须在同一个 web.xml
文件中通过 URL 映射进行映射。这是标准的 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 应用程序的 WEB-INF
目录中查找名为 [servlet-name]-servlet.xml 的文件,并在 Spring 容器中创建其中定义的 bean。在前面的示例中,它查找 '/WEB-INF/spring-ws-servlet.xml'。此文件包含所有 Spring Web Services bean,例如 endpoint、marshaller 等。
作为 web.xml
的替代方案,如果你在 Servlet 3+ 环境中运行,你可以通过编程方式配置 Spring-WS。为此,Spring-WS 提供了许多扩展 Spring Framework 中 WebApplicationInitializer
接口的抽象基类。如果你也使用 @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
类)中找到。其他 bean 定义(通常是服务、仓库等)可以在 MyRootConfig
类中找到。默认情况下,AbstractAnnotationConfigMessageDispatcherServletInitializer
将 servlet 映射到两个模式:/services
和 *.wsdl
,尽管您可以通过覆盖 getServletMappings()
方法来更改此设置。有关 MessageDispatcherServlet
的编程配置的更多详细信息,请参阅 AbstractMessageDispatcherServletInitializer
和 AbstractAnnotationConfigMessageDispatcherServletInitializer
的 Javadoc。
自动公开 WSDL
MessageDispatcherServlet
会自动检测在其 Spring 容器中定义的任何 WsdlDefinition
bean。所有检测到的 WsdlDefinition
bean 也通过 WsdlDefinitionHandlerAdapter
公开。这是一种通过定义一些 bean 来向客户端公开 WSDL 的便捷方式。
举个例子,考虑以下在 Spring-WS 配置文件(/WEB-INF/[servlet-name]-servlet.xml
)中定义的 <static-wsdl>
定义。请注意 id
属性的值,因为它在公开 WSDL 时会被使用。
<sws:static-wsdl id="orders" location="orders.wsdl"/>
或者,它也可以是 @Configuration
类中的一个 @Bean
方法
@Bean
public SimpleWsdl11Definition orders() {
return new SimpleWsdl11Definition(new ClassPathResource("orders.wsdl"));
}
您可以通过向以下形式的 URL 发送 GET
请求(请适当替换主机、端口和 servlet 上下文路径)来访问 classpath 中 orders.wsdl
文件中定义的 WSDL
http://localhost:8080/spring-ws/orders.wsdl
所有 WsdlDefinition bean 定义都会被 MessageDispatcherServlet 以其 bean 名称加上 .wsdl 后缀的形式公开。因此,如果 bean 名称是 echo ,主机名是 server ,Servlet 上下文(war 名称)是 spring-ws ,则 WSDL 可以在 http://server/spring-ws/echo.wsdl 找到。 |
MessageDispatcherServlet
(或更准确地说,WsdlDefinitionHandlerAdapter
)的另一个很棒的特性是,它可以转换其公开的所有 WSDL 的 location
值,以反映传入请求的 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
一样简单。
查阅 WsdlDefinitionHandlerAdapter
类的类级别 Javadoc,了解有关整个转换过程的更多信息。
除了手动编写 WSDL 并使用 <static-wsdl>
公开它之外,Spring Web Services 还可以从 XSD schema 生成 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 schema 构建 WSDL。它遍历 schema 中找到的所有 element
元素,并为所有元素创建一个 message
。接下来,它为所有以定义的请求或响应后缀结尾的消息创建一个 WSDL operation
。默认的请求后缀是 Request
。默认的响应后缀是 Response
,不过可以通过在 <dynamic-wsdl />
上分别设置 requestSuffix
和 responseSuffix
属性来更改。它还根据操作构建一个 portType
、binding
和 service
。
例如,如果我们的 Orders.xsd
schema 定义了 GetOrdersRequest
和 GetOrdersResponse
元素,<dynamic-wsdl>
会创建一个 GetOrdersRequest
和 GetOrdersResponse
消息以及一个 GetOrders
操作,该操作被放入一个 Orders
port type 中。
要使用多个 schema(无论是通过 include 还是 import),您可以将 Commons XMLSchema 放在 classpath 上。如果 Commons XMLSchema 在 classpath 上,<dynamic-wsdl>
元素会遵循所有 XSD 的 import 和 include,并将它们作为单个 XSD 内联到 WSDL 中。这极大地简化了 schema 的部署,同时仍然可以单独编辑它们。
尽管在运行时从 XSD 创建 WSDL 很方便,但这种方法也有一些缺点。首先,尽管我们努力保持 WSDL 生成过程在不同版本之间的一致性,但它仍然有可能(轻微地)发生变化。其次,生成过程有点慢,不过一旦生成,WSDL 就会被缓存以供后续引用。 |
因此,您应该只在项目开发阶段使用 <dynamic-wsdl>
。我们建议使用您的浏览器下载生成的 WSDL,将其存储在项目中,然后使用 <static-wsdl>
公开它。这是确保 WSDL 不随时间变化的最可靠方法。
5.2.2. 在 DispatcherServlet
中配置 Spring-WS
作为 MessageDispatcherServlet
的替代方案,您可以在标准的 Spring-Web MVC DispatcherServlet
中配置一个 MessageDispatcher
。默认情况下,DispatcherServlet
只能委托给 Controllers
,但我们可以通过向 servlet 的 Web 应用程序上下文添加 WebServiceMessageReceiverHandlerAdapter
来指示它委托给 MessageDispatcher
<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
。
类似地,您可以配置一个 WsdlDefinitionHandlerAdapter
,以确保 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
中。此消息监听器需要 WebServiceMessageFactory
和 MessageDispatcher
才能运行。以下配置示例展示了这一点
<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
配置主机名,storeUri
指示要监视请求的邮件文件夹(通常是 POP3 或 IMAP 文件夹),以及 transportUri
,transportUri
指示用于发送响应的服务器(通常是 SMTP 服务器)。
您可以使用可插入策略配置 MailMessageReceiver
如何监视传入消息:即 MonitoringStrategy
。默认情况下使用轮询策略,即每五分钟轮询一次收件箱是否有新消息。您可以通过设置策略上的 pollingInterval
属性来更改此间隔。默认情况下,所有 MonitoringStrategy
实现都会删除已处理的消息。您可以通过设置 deleteMessages
属性来更改此设置。
作为效率较低的轮询方法的替代方案,有一种使用 IMAP IDLE 的监视策略。IDLE 命令是 IMAP 电子邮件协议的一个可选扩展,它允许邮件服务器将新消息更新异步发送到 MailMessageReceiver
。如果您使用的 IMAP 服务器支持 IDLE 命令,您可以将 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 <[email protected]>"/>
<property name="storeUri" value="imap://server:[email protected]/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
)。您只需要定义一个服务器实例并配置它来处理传入请求。Spring 核心框架中的 remoting 模块包含一个方便的 HTTP 服务器工厂 bean:SimpleHttpServerFactoryBean
。最重要的属性是 contexts
,它将上下文路径映射到相应的 HttpHandler
实例。
Spring Web Services 提供了 HttpHandler
接口的两种实现:WsdlDefinitionHttpHandler
和 WebServiceMessageReceiverHttpHandler
。前者将传入的 GET 请求映射到 WsdlDefinition
。后者负责处理 Web 服务消息的 POST 请求,因此需要 WebServiceMessageFactory
(通常是 SaajSoapMessageFactory
)和 WebServiceMessageReceiver
(通常是 SoapMessageDispatcher
)来完成其任务。
与 servlet 世界进行类比,contexts
属性扮演着 web.xml
中 servlet 映射的角色,而 WebServiceMessageReceiverHttpHandler
等同于 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 的支持与其他传输非常相似:有一个用于 WebServiceTemplate
的 XmppMessageSender
和一个用于 MessageDispatcher
的 XmppMessageReceiver
。
以下示例展示了如何设置服务器端 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.3. 端点
端点是 Spring-WS 服务器端支持的核心概念。端点提供对应用程序行为的访问,这些行为通常由业务服务接口定义。端点解释 XML 请求消息,并使用该输入(通常)调用业务服务上的方法。该服务调用的结果表示为响应消息。Spring-WS 有各种各样的端点,并使用不同的方式来处理 XML 消息和创建响应。
您可以通过使用 @Endpoint
注解标记类来创建一个端点。在该类中,您可以定义一个或多个方法来处理传入的 XML 请求,使用各种参数类型(例如 DOM 元素、JAXB2 对象等)。您可以使用另一个注解(通常是 @PayloadRoot
)来指示方法可以处理哪种消息。
考虑以下示例端点
package samples;
@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 注解)作为参数。这意味着消息的 payload 以 DOM 元素的形式传递给此方法。该方法的返回类型为 void ,表示不发送响应消息。有关端点方法的更多信息,请参阅 @Endpoint 处理方法。 |
4 | getOrder 方法接受一个 OrderRequest (也使用 @RequestPayload 注解)作为参数。此参数是 JAXB2 支持的对象(它使用 @XmlRootElement 注解)。这意味着消息的 payload 作为反序列化对象传递给此方法。SoapHeader 类型也作为参数给出。调用时,此参数包含请求消息的 SOAP header。该方法也使用 @ResponsePayload 注解,指示返回值(即 Order )用作响应消息的 payload。有关端点方法的更多信息,请参阅 @Endpoint 处理方法。 |
5 | 此端点的两个处理方法都使用 @PayloadRoot 标记,指示该方法可以处理哪种请求消息:对于 payload 根元素的本地名称为 orderRequest 且 namespace URI 为 http://samples 的请求,将调用 getOrder 方法。对于 payload 根元素的本地名称为 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 一样,默认情况下范围为 singleton。也就是说,每个容器会创建一个 bean 定义的实例。作为 singleton 意味着多个线程可以同时使用它,因此端点必须是线程安全的。如果您想使用不同的范围,例如 prototype,请参阅 Spring 参考文档。 |
Note that all abstract base classes provided in Spring-WS are thread safe, unless otherwise indicated in the class-level Javadoc.
5.3.1. @Endpoint
处理方法
要让一个端点实际处理传入的 XML 消息,它需要有一个或多个处理方法。处理方法可以接受各种参数和返回类型。然而,它们通常有一个包含消息 payload 的参数,并返回响应消息的 payload(如果有)。本节介绍了支持的参数和返回类型。
要指示方法可以处理哪种消息,该方法通常使用 @PayloadRoot
或 @SoapAction
注解进行标记。您可以在 端点映射 中了解有关这些注解的更多信息。
以下示例展示了一个处理方法
@PayloadRoot(localPart = "order", namespace = "http://samples")
public void order(@RequestPayload Element orderElement) {
Order order = createOrder(orderElement);
orderService.createOrder(order);
}
order
方法接受一个 Element
(使用 @RequestPayload
注解)作为参数。这意味着消息的 payload 以 DOM 元素的形式传递给此方法。该方法的返回类型为 void
,表示不发送响应消息。
处理方法参数
处理方法通常有一个或多个参数,这些参数指向传入 XML 消息的不同部分。最常见的情况是,处理方法有一个参数映射到消息的 payload,但它也可以映射到请求消息的其他部分,例如 SOAP header。本节描述了您可以在处理方法签名中使用的参数。
要将参数映射到请求消息的 payload,您需要使用 @RequestPayload
注解标记此参数。此注解告诉 Spring-WS 该参数需要绑定到请求 payload。
下表描述了支持的参数类型。它显示了支持的类型、参数是否应使用 @RequestPayload
注解以及任何附加说明。
名称 | 支持的参数类型 | @RequestPayload 需要吗? |
附加说明 |
---|---|---|---|
TrAX |
|
是 |
默认启用。 |
W3C DOM |
|
是 |
默认启用 |
dom4j |
|
是 |
当 classpath 中存在 dom4j 时启用。 |
JDOM |
|
是 |
当 classpath 中存在 JDOM 时启用。 |
XOM |
|
是 |
当 classpath 中存在 XOM 时启用。 |
StAX |
|
是 |
当 classpath 中存在 StAX 时启用。 |
XPath |
任何 boolean、double、 |
否 |
默认启用,请参阅 |
消息上下文 |
|
否 |
默认启用。 |
SOAP |
|
否 |
默认启用。 |
JAXB2 |
任何使用 |
是 |
当 classpath 中存在 JAXB2 时启用。 |
OXM |
Spring OXM |
是 |
指定 |
接下来几个示例展示了可能的方法签名。以下方法以请求消息的 payload 作为 DOM org.w3c.dom.Element
进行调用
public void handle(@RequestPayload Element element)
以下方法以请求消息的 payload 作为 javax.xml.transform.dom.DOMSource
进行调用。header
参数绑定到请求消息的 SOAP header。
public void handle(@RequestPayload DOMSource domSource, SoapHeader header)
以下方法以请求消息的 payload 反序列化到 MyJaxb2Object
中(使用 @XmlRootElement
注解)进行调用。消息的 payload 也作为 DOM Element
提供。整个消息上下文作为第三个参数传递。
public void handle(@RequestPayload MyJaxb2Object requestObject, @RequestPayload Element element, Message messageContext)
如您所见,在定义如何处理方法签名时有很多可能性。您甚至可以扩展此机制以支持您自己的参数类型。查阅 DefaultMethodEndpointAdapter
和 MethodArgumentResolver
的 Javadoc,了解如何实现。
@XPathParam
一种参数类型需要一些额外的解释:@XPathParam
。这里的想法是,您可以使用 XPath 表达式注解一个或多个方法参数,并且每个这样的注解参数都绑定到表达式的求值结果。以下示例展示了如何实现
package samples;
@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
}
}
由于我们在 XPath 表达式中使用了 s
前缀,因此必须将其绑定到 http://samples
namespace。这可以通过 @Namespace
注解来实现。或者,我们可以将此注解放在类型级别,以便所有处理方法都使用相同的 namespace 映射,甚至可以放在包级别(在 package-info.java
中),以便多个端点使用。
通过使用 @XPathParam
,您可以绑定到 XPath 支持的所有数据类型
-
boolean
或Boolean
-
double
或Double
-
String
-
Node
-
NodeList
除了此列表之外,您还可以使用任何可以通过 Spring 转换服务从 String
转换的类型。
处理方法返回类型
要发送响应消息,处理方法需要指定返回类型。如果不需要响应消息,方法可以声明 void
返回类型。最常见的是,返回类型用于创建响应消息的 payload。然而,您也可以映射到响应消息的其他部分。本节描述了您可以在处理方法签名中使用的返回类型。
要将返回值映射到响应消息的 payload,您需要使用 @ResponsePayload
注解标记该方法。此注解告诉 Spring-WS 返回值需要绑定到响应 payload。
下表描述了支持的返回类型。它显示了支持的类型、参数是否应使用 @ResponsePayload
注解以及任何附加说明。
名称 | 支持的返回类型 | @ResponsePayload 需要吗? |
附加说明 |
---|---|---|---|
无响应 |
|
否 |
默认启用。 |
TrAX |
|
是 |
默认启用。 |
W3C DOM |
|
是 |
默认启用 |
dom4j |
|
是 |
当 classpath 中存在 dom4j 时启用。 |
JDOM |
|
是 |
当 classpath 中存在 JDOM 时启用。 |
XOM |
|
是 |
当 classpath 中存在 XOM 时启用。 |
JAXB2 |
任何使用 |
是 |
当 classpath 中存在 JAXB2 时启用。 |
OXM |
Spring OXM |
是 |
指定 |
在定义处理方法签名时有很多可能性。甚至可以扩展此机制以支持您自己的参数类型。查阅 DefaultMethodEndpointAdapter
和 MethodReturnValueHandler
的类级别 Javadoc,了解如何实现。
5.4. 端点映射
端点映射负责将传入消息映射到适当的端点。默认启用了一些端点映射,例如 PayloadRootAnnotationMethodEndpointMapping
或 SoapActionAnnotationMethodEndpointMapping
。然而,我们首先需要了解 EndpointMapping
的一般概念。
一个 EndpointMapping
提供一个 EndpointInvocationChain
,其中包含与传入请求匹配的端点,并且可能还包含应用于请求和响应的端点拦截器列表。当请求传入时,MessageDispatcher
将其交给端点映射,让它检查请求并生成适当的 EndpointInvocationChain
。然后 MessageDispatcher
调用链中的端点和任何拦截器。
可配置的端点映射的概念,它可以选择性地包含拦截器(拦截器又可以操作请求、响应或两者),这是非常强大的。许多支持功能可以构建到自定义的 EndpointMapping
实现中。例如,自定义端点映射不仅可以根据消息内容选择端点,还可以根据特定的 SOAP header(或者实际上是多个 SOAP header)来选择端点。
大多数端点映射继承自 AbstractEndpointMapping
,该类提供了一个“interceptors”属性,它是要使用的拦截器列表。 拦截请求——EndpointInterceptor
接口 中讨论了 EndpointInterceptors
。此外,还有一个 defaultEndpoint
,当此端点映射未能找到匹配的端点时使用它作为默认端点。
如 端点 中所述,@Endpoint
风格允许您在一个端点类中处理多个请求。这是 MethodEndpointMapping
的职责。此映射决定了应为传入请求消息调用哪个方法。
有两种端点映射可以将请求导向方法:PayloadRootAnnotationMethodEndpointMapping
和 SoapActionAnnotationMethodEndpointMapping
。您可以通过在应用程序上下文中使用 <sws:annotation-driven/>
来启用这两种方法。
PayloadRootAnnotationMethodEndpointMapping
使用 @PayloadRoot
注解,以及 localPart
和 namespace
元素,来标记具有特定限定名的方法。每当传入消息的 payload 根元素具有此限定名时,就会调用该方法。有关示例,请参阅 上文。
或者,SoapActionAnnotationMethodEndpointMapping
使用 @SoapAction
注解来标记具有特定 SOAP Action 的方法。每当传入消息带有此 SOAPAction
header 时,就会调用该方法。
5.4.1. WS-Addressing
WS-Addressing 指定了一种传输中立的路由机制。它基于 To
和 Action
SOAP header,它们分别指示 SOAP 消息的目的地和意图。此外,WS-Addressing 允许您定义返回地址(用于普通消息和故障)以及唯一的 message identifier,可用于关联。有关 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
,而 action 设置为 http://example.com/fabrikam/mail/Delete
。此外,还有一个 message identifier 和一个 reply-to 地址。默认情况下,此地址是“匿名”地址,表示应使用与请求相同的通道(即 HTTP 响应)发送响应,但它也可以是另一个地址,如本示例所示。
在 Spring Web Services 中,WS-Addressing 被实现为端点映射。通过使用此映射,您可以将 WS-Addressing action 与端点关联,类似于前面描述的 SoapActionAnnotationMethodEndpointMapping
。
使用 AnnotationActionEndpointMapping
AnnotationActionEndpointMapping
类似于 SoapActionAnnotationMethodEndpointMapping
,但它使用 WS-Addressing header 而不是 SOAP Action 传输 header。
要使用 AnnotationActionEndpointMapping
,请使用 @Action
注解标记处理方法,类似于 @Endpoint
处理方法 和 端点映射 中描述的 @PayloadRoot
和 @SoapAction
注解。以下示例展示了如何实现
package samples;
@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-Addressing Action
为 http://samples/RequestOrder
的请求路由到 getOrder
方法。WS-Addressing Action
为 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
header 属性进行比较。
最后,还有 messageSenders
属性,它是将响应消息发送到非匿名、带外地址所必需的。您可以在此属性中设置 MessageSender
实现,就像在 WebServiceTemplate
上设置一样。请参阅 URI 和传输。
5.4.2. 拦截请求——EndpointInterceptor
接口
端点映射机制具有端点拦截器的概念。当您想对某些请求应用特定功能时,这些拦截器会非常有用——例如,处理与安全相关的 SOAP header 或请求和响应消息的日志记录。
端点拦截器通常通过在应用程序上下文中使用 <sws:interceptors>
元素来定义。在此元素中,您可以定义适用于该应用程序上下文中定义的所有端点的端点拦截器 bean。或者,您可以使用 <sws:payloadRoot>
或 <sws:soapAction>
元素来指定拦截器应适用于哪个 payload root 名称或 SOAP action。以下示例展示了如何实现
<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
),它拦截所有请求和响应。我们还定义了一个拦截器,它仅适用于 payload root namespace 为 http://www.example.com
的 XML 消息。除了 namespaceUri
,我们还可以定义一个 localPart
属性,以进一步限制拦截器适用的消息。最后,我们定义了两个拦截器,当消息具有 http://www.example.com/SoapAction
SOAP action 时,它们将应用。注意第二个拦截器实际上是对 <interceptors>
元素外部 bean 定义的引用。您可以在 <interceptors>
元素内部的任何位置使用 bean 引用。
当您使用 @Configuration
类时,您可以扩展 WsConfigurerAdapter
来添加拦截器
@Configuration
@EnableWs
public class MyWsConfiguration extends WsConfigurerAdapter {
@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(new MyPayloadRootInterceptor());
}
}
拦截器必须实现 org.springframework.ws.server
包中的 EndpointInterceptor
接口。此接口定义了三个方法:一个用于处理实际端点处理**之前**的请求消息,一个用于处理正常响应消息,另一个用于处理故障消息。后两个方法在端点处理**之后**调用。这三个方法应提供足够的灵活性来执行各种前处理和后处理。
拦截器的 handleRequest(..)
方法返回一个 boolean 值。您可以使用此方法中断或继续调用链的处理。当此方法返回 true
时,端点处理链将继续。当它返回 false
时,MessageDispatcher
将其解释为拦截器本身已处理了事务,并且不再继续处理调用链中的其他拦截器和实际端点。handleResponse(..)
和 handleFault(..)
方法也具有 boolean 返回值。当这些方法返回 false
时,响应将不会发送回客户端。
您可以在 Web 服务中使用许多标准的 EndpointInterceptor
实现。此外,还有 XwsSecurityInterceptor
,其描述见 XwsSecurityInterceptor
。
PayloadLoggingInterceptor
和 SoapEnvelopeLoggingInterceptor
开发 Web 服务时,记录传入和传出的 XML 消息会很有用。Spring WS 通过 PayloadLoggingInterceptor
和 SoapEnvelopeLoggingInterceptor
类提供了便利。前者仅将消息的 payload 记录到 Commons Logging Log 中。后者记录整个 SOAP envelope,包括 SOAP header。以下示例展示了如何在端点映射中定义 PayloadLoggingInterceptor
<sws:interceptors>
<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
</sws:interceptors>
这两个拦截器都有两个属性,logRequest
和 logResponse
,可以设置为 false
以禁用请求或响应消息的日志记录。
您也可以使用前面描述的 WsConfigurerAdapter
方法来实现 PayloadLoggingInterceptor
。
PayloadValidatingInterceptor
使用契约优先开发风格的好处之一是我们可以使用 schema 来验证传入和传出的 XML 消息。Spring-WS 通过 PayloadValidatingInterceptor
提供了便利。此拦截器需要引用一个或多个 W3C XML 或 RELAX NG schema,并且可以设置为验证请求、响应或两者。
注意,请求验证听起来可能是一个好主意,但这会使结果的 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>
当然,你也可以像前面描述的那样,对 PayloadValidatingInterceptor
使用 WsConfigurerAdapter
方法。
使用 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
转换响应消息。注意,由于端点拦截器注册在端点映射级别,你可以创建一个适用于“旧式”消息的端点映射,并将拦截器添加到该映射中。因此,转换仅适用于这些“旧式”消息。
你也可以像前面描述的那样,对 PayloadTransformingInterceptor
使用 WsConfigurerAdapter
方法。
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 Fault
<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;
@SoapFault(faultCode = FaultCode.SERVER)
public class MyBusinessException extends Exception {
public MyClientException(String message) {
super(message);
}
}
每当在端点调用期间抛出带有构造函数字符串 "Oops!"
的 MyBusinessException
时,它会导致以下响应
<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
的典型用法是:。
-
通过调用
MockWebServiceClient.createClient(ApplicationContext)
或MockWebServiceClient.createClient(WebServiceMessageReceiver, WebServiceMessageFactory)
创建一个MockWebServiceClient
实例。 -
通过调用
sendRequest(RequestCreator)
发送请求消息,可能使用RequestCreators
中提供的默认RequestCreator
实现(可以静态导入)。 -
通过调用
andExpect(ResponseMatcher)
设置响应期望,可能使用ResponseMatchers
中提供的默认ResponseMatcher
实现(可以静态导入)。可以通过链式调用andExpect(ResponseMatcher)
设置多个期望。
注意,MockWebServiceClient (及相关类)提供了“流畅”API,因此你通常可以使用 IDE 中的代码补全功能来指导你完成设置模拟服务器的过程。 |
另请注意,在单元测试中,你可以依赖 Spring Web Services 中提供的标准日志记录功能。有时,检查请求或响应消息以找出特定测试失败的原因可能很有用。有关更多信息,请参阅 消息日志记录和跟踪。 |
例如,考虑以下 Web 服务端点类
@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 使用 @Endpoint 进行了注解。参见 端点。 |
2 | getCustomerCount() 方法接受一个 CustomerCountRequest 作为其参数,并返回一个 CustomerCountResponse 。这两个类都是由 Marshaller 支持的对象。例如,它们可以有一个 @XmlRootElement 注解以被 JAXB2 支持。 |
以下示例显示了 CustomerEndpoint
的典型测试
@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 并静态导入了 RequestCreators 和 ResponseMatchers 。 |
2 | 此测试使用了 Spring Framework 中提供的标准测试工具。这不是必需的,但通常是设置测试的最简单方法。 |
3 | 应用程序上下文是一个标准的 Spring-WS 应用程序上下文(参见 MessageDispatcherServlet ),从 spring-ws-servlet.xml 读取。在这种情况下,应用程序上下文包含 CustomerEndpoint 的 bean 定义(或者可能使用了 <context:component-scan /> )。 |
4 | 在 @Before 方法中,我们使用 createClient 工厂方法创建了一个 MockWebServiceClient 。 |
5 | 我们通过调用 sendRequest() 发送请求,使用静态导入的 RequestCreators 提供的 withPayload() RequestCreator (参见 使用 RequestCreator 和 RequestCreators )。我们还通过调用 测试的这一部分可能看起来有点令人困惑,但你的 IDE 的代码补全功能会很有帮助。在输入 |
5.6.2. 使用 RequestCreator
和 RequestCreators
最初,MockWebServiceClient
需要为端点创建一个供其消费的请求消息。客户端为此目的使用了 RequestCreator
策略接口。
public interface RequestCreator {
WebServiceMessage createRequest(WebServiceMessageFactory messageFactory)
throws IOException;
}
你可以编写自己的此接口实现,使用消息工厂创建一个请求消息,但你当然不必这样做。RequestCreators
类提供了一种方法,可以在 withPayload()
方法中基于给定的负载创建 RequestCreator
。你通常会静态导入 RequestCreators
。
5.6.3. 使用 ResponseMatcher
和 ResponseMatchers
当请求消息被端点处理并收到响应后,MockWebServiceClient
可以验证此响应消息是否符合某些期望。客户端为此目的使用了 ResponseMatcher
策略接口。
public interface ResponseMatcher {
void match(WebServiceMessage request,
WebServiceMessage response)
throws IOException, AssertionError;
}
再次,你可以编写自己的此接口实现,当消息不符合你的期望时抛出 AssertionError
实例,但你当然不必这样做,因为 ResponseMatchers
类提供了标准的 ResponseMatcher
实现供你在测试中使用。你通常会静态导入此类。
ResponseMatchers
类提供了以下响应匹配器。
ResponseMatchers 方法 |
描述 |
---|---|
|
期望给定的响应负载。 |
|
期望响应负载能够根据给定的 XSD 模式进行验证。 |
|
期望给定的 XPath 表达式存在、不存在或计算为给定的值。 |
|
期望响应消息中存在给定的 SOAP 头部。 |
|
期望响应消息不包含 SOAP Fault。 |
|
期望响应消息包含特定的 SOAP Fault。 |
你可以通过链式调用 andExpect()
设置多个响应期望。
mockClient.sendRequest(...).
andExpect(payload(expectedResponsePayload)).
andExpect(validPayload(schemaResource));
有关 ResponseMatchers
提供的响应匹配器的更多信息,请参阅 Javadoc。
6. 在客户端使用 Spring Web Services
Spring-WS 提供了一个客户端 Web 服务 API,允许以一致的、XML 驱动的方式访问 Web 服务。它还支持使用 marshaller 和 unmarshaller,以便你的服务层代码可以完全处理 Java 对象。
org.springframework.ws.client.core
包提供了使用客户端访问 API 的核心功能。它包含简化 Web 服务使用的模板类,就像 Spring 核心的 JdbcTemplate
对 JDBC 所做的那样。Spring 模板类共同的设计原则是提供辅助方法来执行常见操作,对于更复杂的用法,则委托给用户实现的 Callback 接口。Web 服务模板遵循相同的设计。这些类为以下方面提供了各种便利方法。
-
发送和接收 XML 消息
-
在发送前将对象编组(Marshalling)为 XML
-
允许多种传输选项
6.1. 使用客户端 API
本节描述如何使用客户端 API。有关如何使用服务器端 API,请参见 使用 Spring-WS 创建 Web 服务。
6.1.1. WebServiceTemplate
WebServiceTemplate
是 Spring-WS 中客户端 Web 服务访问的核心类。它包含发送 Source
对象以及将响应消息接收为 Source
或 Result
的方法。此外,它可以在通过传输发送对象之前将对象编组(Marshal)为 XML,并将任何响应 XML 再次解组(Unmarshal)为对象。
URI 和传输
WebServiceTemplate
类使用 URI 作为消息目标。你可以在模板本身上设置 defaultUri
属性,或在调用模板方法时显式提供 URI。URI 被解析为 WebServiceMessageSender
,它负责通过传输层发送 XML 消息。你可以使用 WebServiceTemplate
类的 messageSender
或 messageSenders
属性设置一个或多个消息发送器。
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 Message
,将其发送到 Queue
或 Topic
上,并接收响应(如果有)。
要使用 JmsMessageSender
,你需要将 defaultUri
或 uri
参数设置为 JMS URI,它至少包含 jms:
前缀和一个目标名称。一些 JMS URI 示例包括:jms:SomeQueue
、jms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENT
和 jms:RequestQueue?replyToName=ResponseName
。有关此 URI 语法的更多信息,请参见 JmsMessageSender
的 Javadoc。
默认情况下,JmsMessageSender
发送 JMS BytesMessage
,但你可以通过使用 JMS URI 上的 messageType
参数来覆盖此设置以使用 TextMessages
,例如 jms:Queue?messageType=TEXT_MESSAGE
。请注意,BytesMessages
是首选类型,因为 TextMessages
不可靠地支持附件和字符编码。
以下示例显示了如何结合 ActiveMQ 连接工厂使用 JMS 传输。
<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
,请将 defaultUri
或 uri
参数设置为 mailto
URI,例如 mailto:[email protected]
或 mailto:server@localhost?subject=SOAP%20Test
。确保消息发送器使用 transportUri
正确配置,该 URI 指示用于发送请求的服务器(通常是 SMTP 服务器),以及 storeUri
,该 URI 指示用于轮询响应的服务器(通常是 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 <[email protected]>"/>
<property name="transportUri" value="smtp://client:[email protected]"/>
<property name="storeUri" value="imap://client:[email protected]/INBOX"/>
</bean>
</property>
<property name="defaultUri" value="mailto:[email protected]?subject=SOAP%20Test"/>
</bean>
</beans>
XMPP 传输
Spring Web Services 2.0 引入了 XMPP (Jabber) 传输,你可以使用它通过 XMPP 发送和接收 Web 服务消息。客户端 XMPP 功能包含在 XmppMessageSender
类中。此类从请求 WebServiceMessage
创建一个 XMPP 消息,并通过 XMPP 发送。然后它监听响应消息的到达。
要使用 XmppMessageSender
,请将 defaultUri
或 uri
参数设置为 xmpp
URI,例如 xmpp:[email protected]
。发送器还需要一个 XMPPConnection
才能工作,这可以通过使用 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:[email protected]"/>
</bean>
</beans>
6.1.2. 发送和接收 WebServiceMessage
WebServiceTemplate
包含许多方便的方法来发送和接收 Web 服务消息。有些方法接受并返回 Source
,有些方法返回 Result
。此外,还有将对象编组(Marshal)和解组(Unmarshal)为 XML 的方法。以下示例发送一个简单的 XML 消息到 Web 服务
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
的 Web 服务发送“Hello, World”消息(在 simpleSendAndReceive()
方法的情况下),并将结果写入控制台。WebServiceTemplate
被注入了默认 URI,该 URI 被使用是因为在 Java 代码中没有显式提供 URI。
注意,一旦配置完成(假设其所有依赖项也是线程安全的,Spring-WS 附带的所有依赖项都是如此),WebServiceTemplate
类就是线程安全的,因此多个对象可以使用相同的共享 WebServiceTemplate
实例。WebServiceTemplate
暴露了一个无参数构造函数以及 messageFactory
和 messageSender
bean 属性,你可以使用它们来构建实例(通过使用 Spring 容器或纯 Java 代码)。或者,考虑派生自 Spring-WS 的 WebServiceGatewaySupport
便利基类,它暴露了方便的 bean 属性以实现轻松配置。(你无需扩展此基类。它仅作为便利类提供。)
6.1.3. 发送和接收 POJO — 编组(Marshalling)和解组(Unmarshalling)
为了方便发送普通的 Java 对象,WebServiceTemplate
有许多 send(..)
方法,它们接受一个 Object
作为消息数据内容的参数。WebServiceTemplate
类中的 marshalSendAndReceive(..)
方法将请求对象转换为 XML 的任务委托给 Marshaller
,并将响应 XML 转换为对象的任务委托给 Unmarshaller
。(有关编组(marshalling)和解组(unmarshaller)的更多信息,请参见 Spring Framework 参考文档。)通过使用 marshaller,你的应用程序代码可以专注于正在发送或接收的业务对象,而无需关心它如何表示为 XML 的细节。要使用编组功能,你必须使用 WebServiceTemplate
类的 marshaller
和 unmarshaller
属性设置一个 marshaller 和一个 unmarshaller。
6.1.4. 使用 WebServiceMessageCallback
为了方便设置 SOAP 头部和消息上的其他设置,WebServiceMessageCallback
接口允许你在消息创建后但在发送前访问它。以下示例演示了如何在通过编组(marshalling)对象创建的消息上设置 SOAP action 头部。
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 action 头部。 |
WS-Addressing
除了服务器端 WS-Addressing 支持外,Spring Web Services 在客户端也支持此规范。
要在客户端设置 WS-Addressing 头部,你可以使用 org.springframework.ws.soap.addressing.client.ActionCallback
。此 callback 接受所需的 action 头部作为参数。它还有指定 WS-Addressing 版本和 To
头部的构造函数。如果未指定,To
头部默认为正在进行的连接的 URL。
以下示例将 Action
头部设置为 http://samples/RequestOrder
webServiceTemplate.marshalSendAndReceive(o, new ActionCallback("http://samples/RequestOrder"));
6.1.5. 使用 WebServiceMessageExtractor
WebServiceMessageExtractor
接口是一个低级 callback 接口,你可以完全控制从接收到的 WebServiceMessage
中提取 Object
的过程。当与提供服务的资源的底层连接仍然打开时,WebServiceTemplate
会调用提供的 WebServiceMessageExtractor
上的 extractData(..)
方法。以下示例展示了 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 服务的类)时,你有两种可能的方法。
-
编写单元测试,它模拟(mock)掉
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
的典型用法是:。
-
通过调用
MockWebServiceServer.createServer(WebServiceTemplate)
、MockWebServiceServer.createServer(WebServiceGatewaySupport)
或MockWebServiceServer.createServer(ApplicationContext)
创建一个MockWebServiceServer
实例。 -
通过调用
expect(RequestMatcher)
设置请求期望,可能使用RequestMatchers
中提供的默认RequestMatcher
实现(可以静态导入)。可以通过链式调用andExpect(RequestMatcher)
设置多个期望。 -
通过调用
andRespond(ResponseCreator)
创建适当的响应消息,可能使用ResponseCreators
中提供的默认ResponseCreator
实现(可以静态导入)。 -
正常使用
WebServiceTemplate
,可以直接使用或通过客户端代码使用。 -
调用
MockWebServiceServer.verify()
以确保所有期望都已满足。
注意,MockWebServiceServer (及相关类)提供了“流畅”API,因此你通常可以使用 IDE 中的代码补全功能来指导你完成设置模拟服务器的过程。 |
另请注意,在单元测试中,你可以依赖 Spring Web Services 中提供的标准日志记录功能。有时,检查请求或响应消息以找出特定测试失败的原因可能很有用。有关更多信息,请参阅 消息日志记录和跟踪。 |
例如,考虑以下 Web 服务客户端类。
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 | CustomerClient 扩展了 WebServiceGatewaySupport ,这为其提供了 webServiceTemplate 属性。 |
2 | CustomerCountRequest 是由 marshaller 支持的对象。例如,它可以有一个 @XmlRootElement 注解以被 JAXB2 支持。 |
3 | CustomerClient 使用 WebServiceGatewaySupport 提供的 WebServiceTemplate 将请求对象编组(marshal)为 SOAP 消息并发送到 Web 服务。响应对象被解组(unmarshal)为 CustomerCountResponse 。 |
以下示例显示了 CustomerClient
的典型测试。
@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 并静态导入了 RequestMatchers 和 ResponseCreators 。 |
2 | 此测试使用了 Spring Framework 中提供的标准测试工具。这不是必需的,但通常是设置测试的最简单方法。 |
3 | CustomerClient 在 integration-test.xml 中配置,并通过 @Autowired 注入到此测试中。 |
4 | 在 @Before 方法中,我们使用 createServer 工厂方法创建了一个 MockWebServiceServer 。 |
5 | 我们通过调用 expect() 定义期望,使用静态导入的 RequestMatchers 提供的 payload() RequestMatcher (参见 使用 RequestMatcher 和 RequestMatchers )。我们还通过调用 测试的这一部分可能看起来有点令人困惑,但你的 IDE 的代码补全功能会很有帮助。在你输入 |
6 | 我们在 CustomerClient 上调用 getCustomerCount() ,从而使用了 WebServiceTemplate 。此时模板已设置为“测试模式”,因此此方法调用不会建立真正的 (HTTP) 连接。我们还根据方法调用的结果进行了一些 JUnit 断言。 |
7 | 我们在 MockWebServiceServer 上调用 verify() ,验证预期的消息是否确实被接收到。 |
6.2.2. 使用 RequestMatcher
和 RequestMatchers
为了验证请求消息是否符合某些期望,MockWebServiceServer
使用了 RequestMatcher
策略接口。此接口定义的契约如下。
public interface RequestMatcher {
void match(URI uri,
WebServiceMessage request)
throws IOException,
AssertionError;
}
你可以编写自己的此接口实现,当消息不符合你的期望时抛出 AssertionError
异常,但你当然不必这样做。RequestMatchers
类提供了标准的 RequestMatcher
实现供你在测试中使用。你通常会静态导入此类。
RequestMatchers
类提供了以下请求匹配器。
RequestMatchers 方法 |
描述 |
---|---|
|
期望任何类型的请求。 |
|
payload() |
|
期望请求负载能够根据给定的 XSD 模式进行验证。 |
|
期望给定的 XPath 表达式存在、不存在或计算为给定的值。 |
|
soapHeader() |
|
期望连接到给定的 URL。 |
你可以通过链式调用 andExpect()
设置多个请求期望。
mockServer.expect(connectionTo("http://example.com")).
andExpect(payload(expectedRequestPayload)).
andExpect(validPayload(schemaResource)).
andRespond(...);
有关 RequestMatchers
提供的请求匹配器的更多信息,请参阅 Javadoc。
6.2.3. 使用 ResponseCreator
和 ResponseCreators
当请求消息已验证并符合定义的期望后,MockWebServiceServer
会创建一个响应消息供 WebServiceTemplate
消费。服务器为此目的使用了 ResponseCreator
策略接口。
public interface ResponseCreator {
WebServiceMessage createResponse(URI uri,
WebServiceMessage request,
WebServiceMessageFactory messageFactory)
throws IOException;
}
再次,你可以编写自己的此接口实现,使用消息工厂创建一个响应消息,但你当然不必这样做,因为 ResponseCreators
类提供了标准的 ResponseCreator
实现供你在测试中使用。你通常会静态导入此类。
ResponseCreators
类提供了以下响应。
ResponseCreators 方法 |
描述 |
---|---|
|
创建一个具有给定负载的响应消息。 |
|
在响应连接中创建错误。此方法使你有机会测试你的错误处理。 |
|
从响应连接读取时抛出异常。此方法使你有机会测试你的异常处理。 |
|
创建一个带有给定 SOAP 错误的响应消息。此方法使你有机会测试你的 Fault 处理。 |
有关 RequestMatchers
提供的请求匹配器的更多信息,请参阅 Javadoc。
7. 使用 Spring-WS 保护你的 Web 服务
本章解释如何为你的 Web 服务添加 WS-Security 方面。我们重点关注 WS-Security 的三个不同领域。
-
认证(Authentication):这是一个确定主体是否是他们声称的身份的过程。在此上下文中,“主体”通常指用户、设备或可以在应用程序中执行操作的其他系统。
-
数字签名(Digital signatures):消息的数字签名是基于文档和签名者私钥的一段信息。它通过使用哈希函数和私有签名函数(使用签名者的私钥加密)创建。
-
加密和解密(Encryption and Decryption):加密是将数据转换为没有相应密钥就无法读取的形式的过程。它主要用于对非目标接收者隐藏信息。解密是加密的反向过程。它是将加密数据转换回可读形式的过程。
这三个领域通过使用 XwsSecurityInterceptor
或 Wss4jSecurityInterceptor
实现,我们分别在 XwsSecurityInterceptor
和 使用 Wss4jSecurityInterceptor
中进行描述。
请注意,WS-Security(尤其是加密和签名)需要大量的内存,并且可能会降低性能。如果性能对您很重要,您可能需要考虑不使用 WS-Security 或改用基于 HTTP 的安全性。 |
7.1. XwsSecurityInterceptor
XwsSecurityInterceptor
是一个 EndpointInterceptor
(请参阅拦截请求 — EndpointInterceptor
接口),它基于 SUN 的 XML 和 Web Services 安全包 (XWSS)。此 WS-Security 实现是 Java Web Services Developer Pack (Java WSDP) 的一部分。
与任何其他 Endpoint 拦截器一样,它在 Endpoint 映射中定义(请参阅Endpoint 映射)。这意味着您可以选择性地添加 WS-Security 支持。某些 Endpoint 映射需要它,而其他则不需要。
请注意,XWSS 需要 SUN 1.5 JDK 和 SUN SAAJ 参考实现。WSS4J 拦截器没有这些要求(请参阅使用 Wss4jSecurityInterceptor )。 |
XwsSecurityInterceptor
需要一个安全策略文件才能运行。这个 XML 文件告诉拦截器需要从传入的 SOAP 消息中要求哪些安全方面,以及向传出的消息中添加哪些方面。以下部分解释了策略文件的基本格式,但您可以在此处找到更深入的教程。您可以使用 policyConfiguration
属性设置策略,该属性需要一个 Spring 资源。策略文件可以包含多个元素——例如,要求传入消息具有用户名令牌并对所有传出消息进行签名。它包含一个 SecurityConfiguration
元素(而不是 JAXRPCSecurity
元素)作为其根元素。
此外,安全拦截器需要一个或多个 CallbackHandler
实例才能运行。这些处理器用于检索证书、私钥、验证用户凭据等。Spring-WS 为大多数常见的安全问题提供了处理器——例如,针对 Spring Security 身份验证管理器进行身份验证,以及基于 X509 证书对传出消息进行签名。以下部分将说明针对特定安全问题使用哪个回调处理器。您可以使用 callbackHandler
或 callbackHandlers
属性设置回调处理器。
以下示例展示了如何配置 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>
此拦截器通过 classpath 中的 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>
如果您不指定位置属性,则会创建一个新的空密钥库,这很可能不是您想要的。 |
KeyStoreCallbackHandler
要在 XwsSecurityInterceptor
中使用密钥库,您需要定义一个 KeyStoreCallbackHandler
。此回调处理器有三个类型为 keystore
的属性:(keyStore
、trustStore
和 symmetricStore
)。处理器使用的具体密钥库取决于此处理器将执行的加密操作。对于私钥操作,使用 keyStore
。对于对称密钥操作,使用 symmetricStore
。对于确定信任关系,使用 trustStore
。下表说明了这一点
加密操作 | 使用的密钥库 |
---|---|
证书验证 |
首先 |
基于私钥的解密 |
|
基于对称密钥的解密 |
|
基于公钥证书的加密 |
|
基于对称密钥的加密 |
|
签名 |
|
签名验证 |
|
此外,KeyStoreCallbackHandler
还有一个 privateKeyPassword
属性,应将其设置为解锁包含在 keyStore
中的私钥。
如果未设置 symmetricStore
,则默认为 keyStore
。如果未设置 key 或 trust store,则回调处理器使用标准的 Java 机制加载或创建它。请参阅 KeyStoreCallbackHandler
的 JavaDoc 以了解此机制如何工作。
例如,如果您想使用 KeyStoreCallbackHandler
验证传入的证书或签名,您可以使用 trust store
<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>
如果您想使用它来解密传入的证书或签署传出消息,您可以使用 key store
<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
,安全策略文件应包含一个 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 故障。如果存在,它会向注册的处理器触发一个带有 PlainTextPasswordRequest
的 PasswordValidationCallback
。在 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
SpringPlainTextPasswordValidationCallbackHandler
使用 Spring Security 进行用户身份验证。本文档的范围不包括描述 Spring Security,但它是一个功能完备的安全框架。您可以在Spring Security 参考文档中阅读更多相关信息。
SpringPlainTextPasswordValidationCallbackHandler
需要一个 AuthenticationManager
才能运行。它使用此管理器对它创建的 UsernamePasswordAuthenticationToken
进行身份验证。如果身份验证成功,令牌将存储在 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
即可运行。它使用此名称创建一个新的 JAAS LoginContext
,并通过使用 SOAP 消息中提供的用户名和密码处理标准的 JAAS NameCallback
和 PasswordCallback
。这意味着此回调处理器与在 login()
阶段触发这些回调的任何 JAAS LoginModule
集成,这是标准行为。
您可以如下所示配置 JaasPlainTextPasswordValidationCallbackHandler
<bean id="jaasValidationHandler"
class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasPlainTextPasswordValidationCallbackHandler">
<property name="loginContextName" value="MyLoginModule" />
</bean>
在这种情况下,回调处理器使用名为 MyLoginModule
的 LoginContext
。此模块应在您的 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 故障。如果存在,它会向注册的处理器触发一个带有 DigestPasswordRequest
的 PasswordValidationCallback
。在 Spring-WS 中,有两个类处理此特定回调:SimplePasswordValidationCallbackHandler
和 SpringDigestPasswordValidationCallbackHandler
。
使用 SimplePasswordValidationCallbackHandler
SimplePasswordValidationCallbackHandler
可以处理明文密码和密码摘要。它在使用 SimplePasswordValidationCallbackHandler
中进行了描述。
使用 SpringDigestPasswordValidationCallbackHandler
SpringDigestPasswordValidationCallbackHandler
需要一个 Spring Security UserDetailService
才能运行。它使用此服务检索令牌中指定用户的密码。然后将此 details 对象中包含的密码摘要与消息中的摘要进行比较。如果它们相等,则用户已成功通过身份验证,并且 UsernamePasswordAuthenticationToken
将存储在 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
,其中包含一个 Base 64 编码的 X509 证书版本。接收方使用该证书进行身份验证。消息中存储的证书也用于对消息进行签名(请参阅验证签名)。
为确保所有传入的 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 术语中,这意味着
使用此设置,拦截器首先使用密钥库确定消息中的证书是否有效,然后再对其进行身份验证。 |
使用 KeyStoreCallbackHandler
KeyStoreCallbackHandler
使用标准的 Java 密钥库来验证证书。此证书验证过程包括以下步骤:.
-
处理器检查证书是否在私有的
keyStore
中。如果在,则它是有效的。 -
如果证书不在私有密钥库中,处理器会检查当前日期和时间是否在证书中给定的有效期内。如果不在,则证书无效。如果在,则继续执行最后一步。
-
为证书创建认证路径。这基本上意味着处理器确定该证书是否由
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>
使用前面示例中所示的设置,要验证的证书必须在 trust store 本身中,或者 trust store 必须包含颁发该证书的证书颁发机构。
使用 SpringCertificateValidationCallbackHandler
SpringCertificateValidationCallbackHandler
需要一个 Spring Security AuthenticationManager
才能运行。它使用此管理器对它创建的 X509AuthenticationToken
进行身份验证。配置的身份验证管理器应提供一个可以处理此令牌的提供者(通常是 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
才能运行。它使用此名称和证书的 X500Principal
创建一个新的 JAAS LoginContext
。这意味着此回调处理器与处理 X500 主体的任何 JAAS LoginModule
集成。
您可以如下所示配置 JaasCertificateValidationCallbackHandler
<bean id="jaasValidationHandler"
class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasCertificateValidationCallbackHandler">
<property name="loginContextName">MyLoginModule</property>
</bean>
在这种情况下,回调处理器使用名为 MyLoginModule
的 LoginContext
。此模块应在您的 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
。在 Spring-WS 中,有一个类处理此特定回调:KeyStoreCallbackHandler
。
使用 KeyStoreCallbackHandler
如KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler
使用 java.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>
XwsSecurityInterceptor
会向注册的处理器触发一个 SignatureKeyCallback
。在 Spring-WS 中,KeyStoreCallbackHandler
类处理此特定回调。
使用 KeyStoreCallbackHandler
如KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler
使用 java.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
。在 Spring-WS 中,KeyStoreCallbackHandler
类处理此特定回调。
使用 KeyStoreCallbackHandler
如KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler
使用 java.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
使用 java.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
分别抛出 WsSecuritySecurementException
或 WsSecurityValidationException
。这些异常绕过标准异常处理机制,但由拦截器自身处理。
WsSecuritySecurementException
异常由 XwsSecurityInterceptor
的 handleSecurementException
方法处理。默认情况下,此方法记录错误并停止消息的进一步处理。
类似地,WsSecurityValidationException
异常由 XwsSecurityInterceptor
的 handleValidationException
方法处理。默认情况下,此方法创建一个 SOAP 1.1 Client 或 SOAP 1.2 sender 故障,并将其作为响应发送回去。
handleSecurementException 和 handleValidationException 都是受保护的方法,您可以覆盖它们以更改其默认行为。 |
7.2. 使用 Wss4jSecurityInterceptor
Wss4jSecurityInterceptor
是一个 EndpointInterceptor
(请参阅拦截请求 — EndpointInterceptor
接口),它基于 Apache 的 WSS4J。
WSS4J 实现了以下标准
-
OASIS Web Services Security: SOAP Message Security 1.0 Standard 200401, March 2004
-
Username Token profile V1.0
-
X.509 Token Profile V1.0
此拦截器支持由 AxiomSoapMessageFactory
和 SaajSoapMessageFactory
创建的消息。
7.2.1. 配置 Wss4jSecurityInterceptor
WSS4J 不使用外部配置文件。拦截器完全通过属性进行配置。此拦截器调用的验证和安全增强操作分别通过 validationActions
和 securementActions
属性指定。操作作为空格分隔的字符串传递。以下清单展示了示例配置
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="UsernameToken Encrypt"/>
...
<property name="securementActions" value="Encrypt"/>
...
</bean>
下表显示了可用的验证操作
验证操作 | 描述 |
---|---|
|
验证用户名令牌 |
|
验证时间戳 |
|
解密消息 |
|
验证签名 |
|
不执行任何操作 |
下表显示了可用的安全增强操作
安全增强操作 | 描述 |
---|---|
|
添加用户名令牌 |
|
添加用户名令牌和用户名令牌秘密密钥签名 |
|
添加时间戳 |
|
加密响应 |
|
签署响应 |
|
不执行任何操作 |
操作的顺序很重要,并由拦截器强制执行。如果其安全操作的执行顺序与 validationActions
指定的顺序不同,拦截器将拒绝传入的 SOAP 消息。
7.2.2. 处理数字证书
对于需要与密钥库或证书处理交互的加密操作(签名、加密和解密操作),WSS4J 需要 org.apache.ws.security.components.crypto.Crypto
的实例。
可以从 WSS4J 的 CryptoFactory
或更方便地使用 Spring-WS 的 CryptoFactoryBean
获取 Crypto
实例。
CryptoFactoryBean
Spring-WS 提供了一个方便的工厂 bean,CryptoFactoryBean
,它通过强类型属性(推荐)或通过 Properties
对象来构造和配置 Crypto
实例。
默认情况下,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
对象配置用户和密码。
回调处理器通过 Wss4jSecurityInterceptor
的 validationCallbackHandler
属性进行配置。
使用 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 Security 的 UserDetailService
验证明文和摘要密码。它使用此服务检索令牌中指定用户的密码(或密码摘要)。然后将此 details 对象中包含的密码(或密码摘要)与消息中的摘要进行比较。如果它们相等,则用户已成功通过身份验证,并且 UsernamePasswordAuthenticationToken
将存储在 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>
添加用户名令牌
向传出消息添加用户名令牌就像将 UsernameToken
添加到 Wss4jSecurityInterceptor
的 securementActions
属性并指定 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>
如果选择明文密码类型,可以通过设置 securementUsernameTokenElements
属性来指示拦截器添加 Nonce
和 Created
元素。该值必须是一个列表,其中包含所需元素的名称,名称之间用空格分隔(区分大小写)。
以下示例生成一个带有明文密码、Nonce
和 Created
元素的用户名令牌
<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 生成包含 X509 证书的 BinarySecurityToken
元素并将其包含在传出消息中。证书的名称和密码分别通过 securementUsername
和 securementPassword
属性传递,如下例所示
<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
中可用的各种时间戳选项。
验证时间戳
要验证时间戳,请将 Timestamp
添加到 validationActions
属性中。您可以通过将 timestampStrict
设置为 true
并通过设置 timeToLive
属性来指定服务器端生存时间(默认为 300 秒),从而覆盖 SOAP 消息发起方指定的时间戳语义。无论 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
中可用的各种签名选项。
验证签名
为了指导 Wss4jSecurityInterceptor
,validationActions
必须包含 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
,可以启用对传出消息的签名。要使用的私钥的别名和密码分别由 securementUsername
和 securementPassword
属性指定。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
属性来自定义要使用的密钥标识符类型。对于签名,只有 IssuerSerial
和 DirectReference
是有效的。
securementSignatureParts
属性控制对消息的哪个部分进行签名。此属性的值是一个以分号分隔的元素名称列表,用于标识要签名的元素。签名部分的通用形式是 {}{namespace}Element
。请注意,第一个空括号仅用于加密部分。默认行为是签署 SOAP 正文。
以下示例展示了如何在 Spring Web Services 的 echo 示例中签署 echoResponse
元素
<property name="securementSignatureParts"
value="{}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>
要指定没有命名空间的元素,请使用字符串 Null
(区分大小写)作为命名空间名称。
如果请求中没有其他元素的本地名称是 Body
,则 SOAP 命名空间标识符可以为空({}
)。
签名确认
通过将 enableSignatureConfirmation
设置为 true
来启用签名确认。请注意,签名确认操作会跨越请求和响应。这意味着即使没有相应的安全操作,secureResponse
和 validateRequest
也必须设置为 true
(这是默认值)。以下示例将 enableSignatureConfirmation
属性设置为 true
<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>
加密
将 Encrypt
添加到 securementActions
可以启用对传出消息的加密。您可以通过设置 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
属性定义。可能的值包括 IssuerSerial
、X509KeyIdentifier
、DirectReference
、Thumbprint
、SKIKeyIdentifier
和 EmbeddedKeyName
。
如果您选择 EmbeddedKeyName
类型,您需要指定用于加密的密钥。密钥的别名在 securementEncryptionUser
属性中设置,与其他密钥标识符类型一样。然而,WSS4J 需要一个回调处理器来获取密钥。因此,您必须为 securementCallbackHandler
提供一个指向相应密钥库的 KeyStoreCallbackHandler
。默认情况下,生成的 WS-Security 头中的 ds:KeyName
元素取 securementEncryptionUser
属性的值。要指示不同的名称,您可以将 securementEncryptionEmbeddedKeyName
设置为所需的值。在下一个示例中,传出消息使用别名为 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-cbc
和 http://www.w3.org/2001/04/xmlenc#aes192-cbc
。
最后,securementEncryptionParts
属性定义了对消息的哪个部分进行加密。此属性的值是一个以分号分隔的元素名称列表,用于标识要加密的元素。每个元素名称前面可以有一个加密模式指示符和一个命名空间标识,每个都包含在一对花括号内。加密模式指示符可以是 {Content}
或 {Element}
。有关 Element 和 Content 加密之间的差异,请参阅 W3C XML 加密规范。以下示例标识了 echo 示例中的 echoResponse
<property name="securementEncryptionParts"
value="{Content}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>
请注意,元素名称、命名空间标识符和加密修饰符是区分大小写的。您可以省略加密修饰符和命名空间标识符。如果省略,加密模式默认为 Content
,并且命名空间设置为 SOAP 命名空间。
要指定没有命名空间的元素,请使用值 Null
(区分大小写)作为命名空间名称。如果未指定列表,处理器默认以 Content
模式加密 SOAP 正文。
7.2.7. 安全异常处理
Wss4jSecurityInterceptor
的异常处理与 XwsSecurityInterceptor
的异常处理相同。有关更多信息,请参阅安全异常处理。
III. 其他资源
参考书目
-
[waldo-94] Jim Waldo, Ann Wollrath, and Sam Kendall. A Note on Distributed Computing. Springer Verlag. 1994
-
[alpine] Steve Loughran & Edmund Smith. Rethinking the Java SOAP Stack. May 17, 2005. © 2005 IEEE Telephone Laboratories, Inc.
-
[effective-enterprise-java] Ted Neward. Scott Meyers. Effective Enterprise Java. Addison-Wesley. 2004
-
[effective-xml] Elliotte Rusty Harold. Scott Meyers. Effective XML. Addison-Wesley. 2004