前言
在当前面向服务的体系结构时代,越来越多的人使用Web服务来连接以前未连接的系统。最初,Web 服务被认为是执行远程过程调用 (RPC) 的另一种方式。然而,随着时间的推移,人们发现 RPC 和 Web 服务之间存在很大差异。特别是当与其他平台的互操作性很重要时,通常最好发送包含处理请求所需的所有数据的封装 XML 文档。从概念上讲,与消息队列相比,基于 XML 的 Web 服务比远程处理解决方案更好。总的来说,XML应该被认为是数据与平台无关的表示形式,是SOA的通用语言。在开发或使用 Web 服务时,重点应该放在这个 XML 上,而不是 Java。
Spring Web Services 专注于创建这些文档驱动的 Web 服务。Spring Web Services 促进了契约优先的 SOAP 服务开发,允许通过使用操作 XML 有效负载的众多方法之一来创建灵活的 Web 服务。Spring-WS提供了一个强大的消息调度框架,一个与现有应用程序安全解决方案集成的WS-Security解决方案,以及一个遵循熟悉的Spring模板模式的客户端API。
一、引言
参考文档的第一部分概述了Spring Web服务和基本概念。然后介绍了 Spring-WS,并解释了契约优先 Web 服务开发背后的概念。
1. 什么是Spring Web Services?
1.1. 简介
Spring Web Services(Spring-WS)是Spring社区的产品,专注于创建文档驱动的Web服务。Spring Web Services 旨在促进契约优先的 SOAP 服务开发,允许通过使用操作 XML 有效负载的多种方法之一来创建灵活的 Web 服务。该产品基于Spring本身,这意味着您可以使用Spring概念(例如依赖注入)作为Web服务的组成部分。
人们使用 Spring-WS 的原因有很多,但大多数人在发现在遵循 Web 服务最佳实践时缺乏替代 SOAP 堆栈后被它所吸引。Spring-WS使最佳实践变得简单。这包括诸如 WS-I 基本配置文件、契约优先开发以及契约和实现之间的松散耦合等实践。Spring Web Services 的其他主要功能包括:
- 强大的映射
- XML API 支持
- 灵活的 XML 编组
- 重用您的弹簧专业知识
- 支持 WS-Security
- 与弹簧安全性集成
- 阿帕奇许可证
1.1.1. 强大的映射
可以将传入的 XML 请求分发到任何对象,具体取决于消息负载、SOAP 操作标头或 XPath 表达式。
1.1.2.XML 接口支持
传入的XML消息不仅可以使用标准的JAXP API(如DOM,SAX和StAX)进行处理,还可以使用JDOM,dom4j,XOM甚至编组技术进行处理。
1.1.3. 灵活的 XML 编组
Spring Web Services 建立在 Spring Framework 中的 Object/XML 映射模块之上,该模块支持 JAXB 1 和 2、Castor、XMLBeans、JiBX 和 XStream。
1.1.4. 重用您的 Spring 专业知识
Spring-WS使用Spring应用程序上下文进行所有配置,这应该有助于Spring开发人员快速上手。此外,Spring-WS的架构类似于Spring-MVC。
1.1.5. 支持 WS 安全
WS-Security 允许您对 SOAP 消息进行签名、加密和解密或针对它们进行身份验证。
1.1.6. 与 Spring 安全性的集成
Spring Web Services 的 WS-Security 实现提供了与 Spring Security 的集成。这意味着您也可以将现有的 Spring 安全性配置用于 SOAP 服务。
1.1.7. 阿帕奇许可证
您可以放心地在项目中使用 Spring-WS。
1.2. 运行时环境
Spring Web Services 需要一个标准的 Java 8 运行时环境。Spring-WS建立在Spring Framework 4.0.9之上,但支持更高的版本。
Spring-WS 由许多模块组成,本节的其余部分将介绍这些模块。
- XML 模块 () 包含 Spring Web Services 的各种 XML 支持类。该模块主要面向 Spring-WS 框架本身,而不是 Web 服务开发人员。
spring-xml.jar
- 核心模块()是Spring的Web服务功能的核心部分。它提供了*WebServiceMessage和SoapMessage接口,服务器端框架(具有强大的消息调度),用于实现Web服务端点的各种支持类以及客户端。spring-ws-core.jarWebServiceTemplate
- 支持模块 () 包含其他传输(JMS、电子邮件等)。spring-ws-support.jar
- 安全包 () 提供了一个与核心 Web 服务包集成的 WS-Security 实现。它允许您对 SOAP 消息进行签名、解密和加密以及添加主体令牌。此外,它还允许您使用现有的 Spring 安全性安全实现进行身份验证和授权。
spring-ws-security.jar
下图显示了 Spring-WS 模块之间的依赖关系。箭头表示依赖关系(也就是说,Spring-WS Core 依赖于 Spring 3 及更高版本中的 Spring-XML 和 OXM 模块)。
1.3. 支持的标准
Spring Web Services 支持以下标准:
- SOAP 1.1 和 1.2
- WSDL 1.1 和 2.0(仅 WSDL 1.1 支持基于 XSD 的生成)
- WS-I 基本配置文件 1.0、1.1、1.2 和 2.0
- WS-Addressing1.0 和 2004 年 8 月草案
- SOAP 消息安全性 1.1、用户名令牌配置文件 1.1、X.509 证书令牌配置文件 1.1、SAML 令牌配置文件 1.1、Kerberos 令牌配置文件 1.1、基本安全配置文件 1.1
2. 为什么要先签订合同?
创建 Web 服务时,有两种开发方式:合同最后和合同优先。当你使用最后的合约方法时,你从Java代码开始,然后让Web服务契约(在WSDL中 - 见侧栏)从中生成。使用协定优先时,从 WSDL 协定开始,然后使用 Java 实现协定。
什么是 WSDL?
WSDL 代表 Web 服务描述语言。WSDL 文件是描述 Web 服务的 XML 文档。它指定服务的位置以及服务公开的操作(或方法)。有关 WSDL 的更多信息,请参阅 WSDL 规范。
Spring-WS 仅支持合约优先的开发风格,本节解释了原因。
2.1. 对象/XML 阻抗不匹配
与ORM领域类似,我们有一个对象/关系阻抗不匹配,将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 不允许这些类型的扩展。java.lang.String
2.1.2. 不可移植的类型
Web服务最重要的目标之一是可互操作:支持多个平台,如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;
}
毫无疑问,此映射的内容可以转换为某种 XML,但由于没有标准方法来描述 XML 中的映射,因此它将是专有的。此外,即使可以将其转换为XML,许多平台也没有类似于.因此,当 .NET 客户端访问您的 Web 服务时,它最终可能会以具有不同语义的 结束。TreeMap
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>
此协定定义一个请求,该请求采用 ,该请求是表示年、月和日的 XSD 数据类型。如果我们从 Java 调用此服务,我们可能会使用 a 或 .但是,这两个类实际上都描述时间,而不是日期。因此,我们实际上最终会在午夜 () 发送表示 2007 年 4 月 4 日的数据,这与 不同。date
java.util.Date
java.util.Calendar
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
}
这是一个循环图:引用 ,它引用再次。像这样的循环图在Java中很常见。如果我们采取一种天真的方法将其转换为XML,我们最终会得到如下结果:Flight
Passenger
Flight
<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/编码)中使用这些引用的标准方法已被弃用,取而代之的是文档/文字(请参阅 WS-I 基本配置文件)。
这些只是处理 O/X 映射时的一些问题。在编写 Web 服务时,尊重这些问题非常重要。尊重它们的最佳方法是完全专注于XML,同时使用Java作为实现语言。这就是合同优先的意义所在。
2.2. 合同优先与合同最后
除了上一节中提到的对象/XML 映射问题之外,还有其他原因使首选契约优先的开发风格。
2.2.1. 脆弱性
如前所述,合同最后开发风格导致您的 Web 服务协定(WSDL 和 XSD)从您的 Java 协定(通常是接口)生成。如果您使用此方法,则无法保证合同在一段时间内保持不变。每次更改 Java 协定并重新部署它时,都可能会对 Web 服务协定进行后续更改。
此外,并非所有 SOAP 堆栈都从 Java 协定生成相同的 Web 服务协定。这意味着将当前 SOAP 堆栈更改为其他堆栈(无论出于何种原因)也可能更改 Web 服务协定。
当 Web 服务协定更改时,必须指示协定的用户获取新协定,并可能更改其代码以适应协定中的任何更改。
为了使合同有用,它必须尽可能长时间地保持不变。如果合同发生更改,您必须联系服务的所有用户,并指示他们获取新版本的合同。
2.2.2. 性能
当 Java 对象自动转换为 XML 时,无法确定通过网络发送的内容。一个对象可能引用另一个对象,而另一个对象引用另一个对象,依此类推。最后,虚拟机堆上的一半对象可能会转换为 XML,从而导致响应时间变慢。
使用协定优先时,您可以显式描述将哪些 XML 发送到何处,从而确保它正是您想要的。
2.2.3. 可重用性
通过在单独的文件中定义架构,可以在不同的方案中重用该文件。考虑在名为 的文件中定义 :AirportCode
airline.xsd
<simpleType name="AirportCode">
<restriction base="string">
<pattern value="[A-Z][A-Z][A-Z]"/>
</restriction>
</simpleType>
通过使用语句,可以在其他模式甚至 WSDL 文件中重用此定义。import
2.2.4. 版本控制
尽管合同必须尽可能长时间地保持不变,但有时确实需要更改它们。在 Java 中,这通常会导致一个新的 Java 接口,例如 ,以及该接口的(新)实现。当然,必须保留旧服务,因为可能存在尚未迁移的客户端。AirlineService2
如果使用合约优先,我们可以在合约和实现之间有一个更松散的耦合。这种更松散的耦合允许我们在一个类中实现合约的两个版本。例如,我们可以使用 XSLT 样式表将任何“旧式”消息转换为“新式”消息。
3. 编写契约优先的 Web 服务
本教程向您展示如何编写协定优先的 Web 服务,即如何开发首先以 XML 模式或 WSDL 协定开头,然后是 Java 代码的 Web 服务。Spring-WS 专注于这种开发风格,本教程应该可以帮助您入门。请注意,本教程的第一部分几乎没有包含特定于 Spring-WS 的信息。它主要是关于XML,XSD和WSDL。第二部分侧重于与 Spring-WS 实现此合同。
在进行契约优先的Web服务开发时,最重要的是从XML的角度来思考。这意味着Java语言概念的重要性较低。它是通过网络发送的 XML,您应该关注这一点。用于实现 Web 服务的 Java 是一个实现细节。
在本教程中,我们将定义由人力资源部门创建的 Web 服务。客户可以向此服务发送假期申请表以预订假期。
3.1. 消息
在本节中,我们将重点介绍发送到 Web 服务和从 Web 服务发送的实际 XML 消息。我们首先确定这些消息的外观。
3.1.1. 假期
在这种情况下,我们必须处理假日请求,因此在 XML 中确定假日的外观是有意义的:
<Holiday xmlns="http://mycompany.com/hr/schemas">
<StartDate>2006-07-03</StartDate>
<EndDate>2006-07-07</EndDate>
</Holiday>
假日由开始日期和结束日期组成。我们还决定对日期使用标准的 ISO 8601 日期格式,因为这节省了很多解析麻烦。我们还为元素添加了一个命名空间,以确保我们的元素可以在其他 XML 文档中使用。
3.1.2. 员工
场景中还有员工的概念。下面是它在 XML 中的样子:
<Employee xmlns="http://mycompany.com/hr/schemas">
<Number>42</Number>
<FirstName>Arjen</FirstName>
<LastName>Poutsma</LastName>
</Employee>
我们使用了与以前相同的命名空间。如果此元素可以在其他方案中使用,则使用其他命名空间(如 .<Employee/>
http://example.com/employees/schemas
3.1.3. 假期请求
元素和元素都可以放在一个 :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
- XML Schema (XSD)
- 放松吴
- 图创
DTD 的命名空间支持有限,因此它们不适合 Web 服务。Relax NG和Schematron比XML Schema更容易。不幸的是,它们在各个平台上没有得到如此广泛的支持。因此,我们使用 XML Schema。
到目前为止,创建 XSD 的最简单方法是从示例文档中推断它。任何好的XML编辑器或Java IDE都提供此功能。基本上,这些工具使用一些示例 XML 文档来生成验证所有文档的架构。最终结果当然需要打磨,但这是一个很好的起点。
使用前面描述的示例,我们最终会生成以下架构:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/hr/schemas"
xmlns:hr="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Holiday"/>
<xs:element ref="hr:Employee"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Holiday">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:StartDate"/>
<xs:element ref="hr:EndDate"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="StartDate" type="xs:NMTOKEN"/>
<xs:element name="EndDate" type="xs:NMTOKEN"/>
<xs:element name="Employee">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Number"/>
<xs:element ref="hr:FirstName"/>
<xs:element ref="hr:LastName"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:NCName"/>
<xs:element name="LastName" type="xs:NCName"/>
</xs:schema>
可以改进此生成的架构。首先要注意的是,每个类型都有一个根级元素声明。这意味着 Web 服务应该能够接受所有这些元素作为数据。这是不可取的:我们只想接受 .通过删除包装元素标签(从而保留类型)并内联结果,我们可以实现这一点,如下所示:<HolidayRequest/>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:hr="http://mycompany.com/hr/schemas"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="Holiday" type="hr:HolidayType"/>
<xs:element name="Employee" type="hr:EmployeeType"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="HolidayType">
<xs:sequence>
<xs:element name="StartDate" type="xs:NMTOKEN"/>
<xs:element name="EndDate" type="xs:NMTOKEN"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="EmployeeType">
<xs:sequence>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:NCName"/>
<xs:element name="LastName" type="xs:NCName"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
架构仍然存在一个问题:使用这样的架构,您可以期望验证以下消息:
<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
<Holiday>
<StartDate>this is not a date</StartDate>
<EndDate>neither is this</EndDate>
</Holiday>
PlainText Section qName:lineannotation level:4, chunks:[<, !-- ... --, >] attrs:[:]
</HolidayRequest>
显然,我们必须确保开始和结束日期确实是日期。XML模式有一个很好的内置类型,我们可以使用它。我们还将 s 更改为实例。最后,我们将 in 更改为 。这会告诉 XML 分析器,和 的顺序并不重要。我们的最终 XSD 现在类似于以下列表:date
NCName
string
sequence
<HolidayRequest/>
all
<Holiday/>
<Employee/>
<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"/>
<xs:element name="Employee" type="hr:EmployeeType"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:complexType name="HolidayType">
<xs:sequence>
<xs:element name="StartDate" type="xs:date"/>
<xs:element name="EndDate" type="xs:date"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="EmployeeType">
<xs:sequence>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
all 告知 XML 分析器,和 的顺序不重要。<Holiday/> <Employee/> |
我们使用数据类型(由年、月和日组成)表示 和 。xs:date <StartDate/> <EndDate/> |
xs:string 用于名字和姓氏。 |
我们将此文件存储为 .hr.xsd
3.3. 服务合同
服务协定通常表示为 WSDL 文件。请注意,在 Spring-WS 中,不需要手动编写 WSDL。基于 XSD 和一些约定,Spring-WS 可以为您创建 WSDL,如标题为实现端点的部分所述。本节的其余部分将介绍如何手动编写 WSDL。您可能需要跳到下一部分。
我们从标准前导码开始我们的 WSDL,并导入我们现有的 XSD。为了将模式与定义分开,我们对 WSDL 定义使用单独的命名空间:。以下清单显示了前导码:http://mycompany.com/hr/definitions
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:schema="http://mycompany.com/hr/schemas"
xmlns:tns="http://mycompany.com/hr/definitions"
targetNamespace="http://mycompany.com/hr/definitions">
<wsdl:types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:import namespace="http://mycompany.com/hr/schemas" schemaLocation="hr.xsd"/>
</xsd:schema>
</wsdl:types>
接下来,我们根据编写的架构类型添加消息。我们只有一条消息,即我们放入模式中的消息:<HolidayRequest/>
<wsdl:message name="HolidayRequest">
<wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>
</wsdl:message>
我们将消息作为操作添加到端口类型:
<wsdl:portType name="HumanResource">
<wsdl:operation name="Holiday">
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>
</wsdl:operation>
</wsdl:portType>
该消息完成了 WSDL 的抽象部分(可以说是接口),并留下了具体部分。具体部分由 a(告诉客户端如何调用您刚刚定义的操作)和 a(告诉客户端在哪里调用它)组成。binding
service
添加混凝土部件是非常标准的。为此,请参考您之前定义的抽象部分,确保用于元素(已弃用),为操作选择一个(在本例中为 ,但任何 URI 都有效),并确定您希望请求到达的 URL(在本例中为 ):document/literal
soap:binding
rpc/encoded
soapAction
http://mycompany.com/RequestHoliday
location
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"
schemaLocation="hr.xsd"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="HolidayRequest">
<wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>
</wsdl:message>
<wsdl:portType name="HumanResource">
<wsdl:operation name="Holiday">
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="HumanResourceBinding" type="tns:HumanResource">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="Holiday">
<soap:operation soapAction="http://mycompany.com/RequestHoliday"/>
<wsdl:input name="HolidayRequest">
<soap:body use="literal"/>
</wsdl:input>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="HumanResourceService">
<wsdl:port binding="tns:HumanResourceBinding" name="HumanResourcePort">
<soap:address location="http://localhost:8080/holidayService/"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
我们导入数据协定中定义的架构。 |
我们定义消息,该消息在 .HolidayRequest portType |
类型在架构中定义。HolidayRequest |
我们定义端口类型,该类型在 .HumanResource binding |
我们定义绑定,该绑定在 .HumanResourceBinding port |
我们使用文档/文字样式。
|
文本表示 HTTP 传输。http://schemas.xmlsoap.org/soap/http |
该属性表示将随每个请求一起发送的 HTTP 标头。soapAction SOAPAction |
地址是可以调用 Web 服务的 URL。http://localhost:8080/holidayService/ |
前面的清单显示了最终的 WSDL。我们将在下一节中介绍如何实现生成的模式和 WSDL。
3.4. 创建项目
在本节中,我们使用Maven为我们创建初始项目结构。这样做不是必需的,但大大减少了我们必须编写的代码量来设置我们的假期服务。
以下命令使用 Spring-WS 原型(即项目模板)为我们创建一个 Maven Web 应用程序项目:
mvn archetype:create -DarchetypeGroupId=org.springframework.ws \
-DarchetypeArtifactId=spring-ws-archetype \
-DarchetypeVersion= \
-DgroupId=com.mycompany.hr \
-DartifactId=holidayService
上述命令创建一个名为 的新目录。此目录中是一个目录,其中包含 WAR 文件的根目录。你可以在这里找到标准的Web应用程序部署描述符(),它定义了一个Spring-WS,并将所有传入的请求映射到这个servlet:holidayService
src/main/webapp
'WEB-INF/web.xml'
MessageDispatcherServlet
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>MyCompany HR Holiday Service</display-name>
<!-- take special notice of the name of this servlet -->
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
除了上述文件之外,您还需要另一个特定于 Spring-WS 的配置文件,名为 .此文件包含所有特定于 Spring-WS 的 bean,例如 和 用于创建新的 Spring 容器。此文件的名称派生自附带的 servlet(在本例中)的名称,并附加在其上。因此,如果您定义 名称 ,则特定于 Spring-WS 的配置文件的名称变为 。WEB-INF/web.xml
WEB-INF/spring-ws-servlet.xml
EndPoints
WebServiceMessageReceivers
'spring-ws'
-servlet.xml
MessageDispatcherServlet
'dynamite'
WEB-INF/dynamite-servlet.xml
(您可以在 [tutorial.example.sws-conf-file] 中看到此示例的文件内容。WEB-INF/spring-ws-servlet.xml
创建项目结构后,可以将上一节中的架构和 WSDL 放入文件夹中。'WEB-INF/'
3.5. 实现端点
在 Spring-WS 中,你实现端点来处理传入的 XML 消息。终结点通常是通过使用注释对类进行批注来创建的。在此终结点类中,可以创建一个或多个处理传入请求的方法。方法签名可以非常灵活。您可以包含与传入 XML 消息相关的几乎任何类型的参数类型,如本章后面所述。@Endpoint
3.5.1. 处理 XML 消息
在此示例应用程序中,我们使用 JDom 2 来处理 XML 消息。我们也使用 XPath,因为它允许我们选择 XML JDOM 树的特定部分,而无需严格的模式一致性。
以下清单显示了定义假日终结点的类:
package com.mycompany.hr.ws;
@Endpoint
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
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")
public void handleHolidayRequest(@RequestPayload Element holidayRequest) throws Exception {
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 + "]");
}
}
}
用 注释。这标志着该类是一种特殊的类,适合在 Spring-WS 中处理 XML 消息,并且也使其有资格用于组件扫描。HolidayEndpoint @Endpoint @Component |
这需要业务服务来操作,所以我们在构造函数中注入依赖关系并用 . 接下来,我们使用 JDOM2 API 设置 XPath 表达式。有四个表达式:用于提取文本值,用于提取结束日期,以及两个用于提取员工姓名的表达式。HolidayEndpoint HumanResourceService @Autowired //hr:StartDate <StartDate> //hr:EndDate |
注解告诉 Spring-WS 该方法适用于处理 XML 消息。此方法可以处理的消息类型由注释值指示。在这种情况下,它可以 处理具有本地部分和命名空间的 XML 元素。 下一节提供了有关将消息映射到终结点的详细信息。@PayloadRoot handleHolidayRequest HolidayRequest http://mycompany.com/hr/schemas |
该方法是主要的处理方法,它从传入的 XML 消息中传递元素。注释指示参数应映射到 请求消息。我们使用 XPath 表达式从 XML 消息中提取字符串值,并使用 (方法) 将这些值转换为对象。使用这些值,我们在业务服务上调用方法。 通常,这会导致启动数据库事务,并在数据库中更改某些记录。 最后,我们定义一个返回类型,它向 Spring-WS 指示我们不想发送响应消息。 如果我们想要响应消息,我们可以返回一个 JDOM 元素来表示响应消息的有效负载。handleHolidayRequest(..) <HolidayRequest/> @RequestPayload holidayRequest Date SimpleDateFormat parseData void |
使用 JDOM 只是处理 XML 的选项之一。其他选项包括DOM,dom4j,XOM,SAX和StAX,以及JAXB,Castor,XMLBeans,JiBX和XStream等编组技术,如下章所述。我们之所以选择 JDOM,是因为它允许我们访问原始 XML,并且因为它基于类(而不是像 W3C DOM 和 dom4j 那样的接口和工厂方法),这使得代码不那么冗长。我们使用 XPath 是因为它比编组技术更不脆弱。我们不需要严格的架构一致性,只要我们能找到日期和名称。
因为我们使用 JDOM,所以我们必须添加一些依赖项到 Maven ,它位于我们项目目录的根目录中。以下是POM的相关部分:pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-core</artifactId>
<version></version>
</dependency>
<dependency>
<groupId>jdom</groupId>
<artifactId>jdom</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1</version>
</dependency>
</dependencies>
以下是我们如何使用组件扫描在 Spring XML 配置文件中配置这些类。我们还指示 Spring-WS 将注释驱动的端点与元素一起使用。spring-ws-servlet.xml
<sws:annotation-driven>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:sws="http://www.springframework.org/schema/web-services"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.mycompany.hr"/>
<sws:annotation-driven/>
</beans>
3.5.2. 将消息路由到端点
作为编写终结点的一部分,我们还使用注释来指示该方法可以处理哪种类型的消息。在 Spring-WS 中,此过程由 .在这里,我们使用 .以下清单显示了我们之前使用的注释:@PayloadRoot
handleHolidayRequest
EndpointMapping
PayloadRootAnnotationMethodEndpointMapping
@PayloadRoot(namespace = "http://mycompany.com/hr/schemas", localPart = "HolidayRequest")
前面示例中所示的注释基本上意味着,每当收到带有命名空间和本地名称的 XML 消息时,都会将其路由到该方法。通过使用配置中的元素,我们可以检测注释。在一个端点中可能(而且很常见)有多个相关的处理方法,每个方法处理不同的 XML 消息。http://mycompany.com/hr/schemas
HolidayRequest
handleHolidayRequest
<sws:annotation-driven>
@PayloadRoot
还有其他方法可以将端点映射到 XML 消息,这将在下一章中介绍。
3.5.3. 提供服务和存根实现
现在我们有了端点,我们需要它的实现以供 .以下清单显示了该接口:HumanResourceService
HolidayEndpoint
HumanResourceService
package com.mycompany.hr.service;
public interface HumanResourceService {
void bookHoliday(Date startDate, Date endDate, String name);
}
出于教程目的,我们使用以下的简单存根实现:HumanResourceService
package com.mycompany.hr.service;
@Service
public class StubHumanResourceService implements HumanResourceService {
public void bookHoliday(Date startDate, Date endDate, String name) {
System.out.println("Booking holiday for [" + startDate + "-" + endDate + "] for [" + name + "] ");
}
}
用 注释。这会将该类标记为业务外观,这使它成为 in 注入的候选项。StubHumanResourceService @Service @Autowired HolidayEndpoint |
3.6. 发布 WSDL
最后,我们需要发布 WSDL。如服务契约中所述,我们不需要自己编写 WSDL。Spring-WS 可以根据一些约定生成一个。以下是我们如何定义世代:
<sws:dynamic-wsdl
portTypeName="HumanResource"
locationUri="/holidayService/"
targetNamespace="http://mycompany.com/hr/definitions">
<sws:xsd location="/WEB-INF/hr.xsd"/>
</sws:dynamic-wsdl>
确定可以检索 WSDL 的 URL。在本例中为 is ,这意味着可以检索 WSDL 就像在 servlet 上下文中一样。完整的网址是 。id id holiday holiday.wsdl http://localhost:8080/holidayService/holiday.wsdl |
接下来,我们将 WSDL 端口类型设置为 。HumanResource |
我们设置了可以到达服务的位置:。我们使用相对 URI,并指示框架对其进行转换 动态到绝对 URI。因此,如果将服务部署到不同的上下文,则不必手动更改 URI。 有关详细信息,请参阅名为“自动 WSDL 公开”的部分。为了使位置转换起作用,我们需要在 servlet 中添加一个 init 参数(显示在下一个列表中)。/holidayService/ spring-ws web.xml |
我们为 WSDL 定义本身定义目标命名空间。不需要设置此属性。如果未设置,则 WSDL 具有与 XSD 架构相同的命名空间。
|
该元素引用我们在数据协定中定义的人力资源架构。我们将架构放在应用程序的目录中。xsd WEB-INF |
下面的清单显示了如何添加 init 参数:
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
可以使用 创建 WAR 文件。如果部署应用程序(部署到 Tomcat、Jetty 等)并将浏览器指向此位置,则会看到生成的 WSDL。此 WSDL 已准备好供客户端使用,例如 soapUI 或其他 SOAP 框架。mvn install
本教程到此结束。教程代码可以在 Spring-WS 的完整发行版中找到。如果要继续,请查看作为分发一部分的 echo 示例应用程序。之后,查看航空公司示例,该示例有点复杂,因为它使用 JAXB、WS-Security、Hibernate 和事务服务层。最后,您可以阅读参考文档的其余部分。