前言
在当前面向服务的架构时代,越来越多的人使用 Web 服务来连接以前未连接的系统。最初,Web 服务被认为是执行远程过程调用 (RPC) 的另一种方式。然而,随着时间的推移,人们发现 RPC 和 Web 服务之间存在很大差异。特别是当与其他平台的互操作性很重要时,通常最好发送包含处理请求所需的所有数据的封装 XML 文档。从概念上讲,与消息队列相比,基于 XML 的 Web 服务比远程处理解决方案更好。总的来说,XML 应该被视为数据平台中立的表示形式,是 SOA 的公共语言。在开发或使用 Web 服务时,重点应该放在这个 XML 上,而不是 Java 上。
Spring Web 服务专注于创建这些文档驱动的 Web 服务。Spring Web 服务促进了契约优先的 SOAP 服务开发,允许通过使用多种方法来操作 XML 有效负载来创建灵活的 Web 服务。Spring-WS 提供了一个强大的消息调度框架,一个与你现有的应用程序安全解决方案集成的 WS-Security 解决方案,以及一个遵循熟悉的 Spring 模板模式的客户端 API。
I. 引言
1. 什么是 Spring Web 服务?
1.1. 简介
Spring Web Services (Spring-WS) 是 Spring 社区的一个产品,专注于创建文档驱动的 Web 服务。Spring Web 服务旨在促进契约优先的 SOAP 服务开发,允许通过使用多种方法来操作 XML 有效负载来创建灵活的 Web 服务。该产品基于 Spring 本身,这意味着您可以将 Spring 概念(例如依赖项注入)用作 Web 服务的组成部分。
人们使用 Spring-WS 的原因有很多,但大多数人在发现在遵循 Web 服务最佳实践时缺乏替代 SOAP 堆栈后,才被它所吸引。Spring-WS 使最佳实践成为一种简单的实践。这包括 WS-I 基本配置文件、契约优先开发以及契约和实现之间的松散耦合等实践。Spring Web 服务的其他主要功能是:
1.1.3. 灵活的 XML 编组
Spring Web 服务构建在 Spring 框架中的 Object/XML 映射模块之上,该模块支持 JAXB 1 和 2、Castor、XMLBeans、JiBX 和 XStream。
1.1.4. 重用您的 Spring 专业知识
Spring-WS 使用 Spring 应用程序上下文进行所有配置,这应该有助于 Spring 开发人员快速上手。此外,Spring-WS 的体系结构类似于 Spring-MVC 的体系结构。
1.2. 运行环境
Spring Web 服务需要标准的 Java 8 运行时环境。Spring-WS 基于 Spring Framework 4.0.9 构建,但支持更高的版本。
Spring-WS 由许多模块组成,这些模块将在本节的其余部分进行描述。
-
XML 模块 () 包含 Spring Web 服务的各种 XML 支持类。该模块主要面向 Spring-WS 框架本身,而不是 Web 服务开发人员。
spring-xml.jar
-
Core 模块 () 是 Spring 的 Web 服务功能的核心部分。它提供中央
WebServiceMessage
和SoapMessage
接口、服务器端框架(具有强大的消息调度功能)、用于实现 Web 服务端点的各种支持类以及客户端。spring-ws-core.jar
WebServiceTemplate
-
Support 模块 () 包含其他传输方式(JMS、Email 等)。
spring-ws-support.jar
-
安全包 () 提供与核心 Web 服务包集成的 WS-Security 实现。它允许您对 SOAP 消息进行签名、解密和加密,以及向 SOAP 消息添加主体令牌。此外,它还允许您使用现有的 Spring Security 安全实现进行身份验证和授权。
spring-ws-security.jar
下图显示了 Spring-WS 模块之间的依赖关系。箭头表示依赖关系(即,Spring-WS Core 依赖于 Spring-XML 和 Spring 3 及更高版本中的 OXM 模块)。
2. 为什么先签订合同?
创建 Web 服务时,有两种开发方式:contract-last 和 contract-first。当您使用 contract-last 方法时,您从 Java 代码开始,然后让 Web 服务契约(在 WSDL 中 — 参见侧边栏)从中生成。使用 contract-first 时,您从 WSDL Contract 开始,并使用 Java 实现该 Contract。
Spring-WS 仅支持 Contract 优先的开发风格,本节将解释原因。
2.1. Object/XML 阻抗不匹配
与 ORM 领域类似,我们遇到了 Object/Relational 阻抗不匹配的情况,将 Java 对象转换为 XML 也有类似的问题。乍一看,O/X 映射问题似乎很简单:为每个 Java 对象创建一个 XML 元素,以将所有 Java 属性和字段转换为子元素或属性。但是,事情并不像看起来那么简单,因为分层语言(如 XML,尤其是 XSD)和 Java 的图形模型之间存在根本差异。
本节中的大部分内容都受到了 [alpine] 和 [effective-enterprise-java] 的启发。 |
2.1.1. XSD 扩展
在 Java 中,更改类行为的唯一方法是将其子类化以将新行为添加到该子类中。在 XSD 中,您可以通过限制数据类型来扩展数据类型,即约束元素和属性的有效值。例如,请考虑以下示例:
<simpleType name="AirportCode">
<restriction base="string">
<pattern value="[A-Z][A-Z][A-Z]"/>
</restriction>
</simpleType>
此类型通过正则表达式限制 XSD 字符串,只允许三个大写字母。如果将此类型转换为 Java,我们最终会得到一个普通的 .正则表达式在转换过程中丢失,因为 Java 不允许这些类型的扩展。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
}
这是一个循环图:the 引用 ,而 则再次引用 。像这样的循环图在 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/编码)已被弃用,取而代之的是 document/literal(参见 WS-I 基本配置文件)。
这些只是处理 O/X 映射时的一些问题。在编写 Web 服务时,尊重这些问题很重要。尊重它们的最佳方法是完全关注 XML,同时使用 Java 作为实现语言。这就是合同优先的意义所在。
2.2. 合约优先与合约后
除了上一节中提到的 Object/XML Mapping 问题外,还有其他原因使您更喜欢 Contract 优先的开发风格。
2.2.1. 脆弱性
如前所述,契约最后的开发风格导致 Web 服务契约(WSDL 和 XSD)从 Java 契约(通常是一个接口)生成。如果使用此方法,则无法保证协定在一段时间内保持不变。每次更改 Java 协定并重新部署它时,Web 服务协定可能会发生后续更改。
此外,并非所有 SOAP 堆栈都从 Java 协定生成相同的 Web 服务协定。这意味着将当前的 SOAP 堆栈更改为不同的 SOAP 堆栈(无论出于何种原因)也可能更改 Web 服务协定。
当 Web 服务协定发生更改时,必须指示协定的用户获取新协定,并可能更改其代码以适应协定中的任何更改。
要使合约有用,它必须尽可能长时间地保持不变。如果合同发生更改,您必须联系服务的所有用户,并指示他们获取新版本的合同。
2.2.2. 性能
当 Java 对象自动转换为 XML 时,无法确定通过网络发送的内容。一个对象可能会引用另一个对象,而另一个对象又引用另一个对象,依此类推。最后,虚拟机中堆上的一半对象可能会转换为 XML,这会导致响应时间变慢。
使用 contract-first 时,您可以明确描述将哪些 XML 发送到何处,从而确保它正是您想要的。
3. 编写契约优先的 Web 服务
本教程将介绍如何编写合同优先的 Web 服务,即如何开发首先以 XML 架构或 WSDL 合同开头,然后以 Java 代码开头的 Web 服务。Spring-WS 侧重于这种开发风格,本教程应该可以帮助您入门。请注意,本教程的第一部分几乎没有 Spring-WS 特定的信息。它主要与 XML、XSD 和 WSDL 有关。第二部分侧重于使用 Spring-WS 实现此 Contract 。
在进行契约优先的 Web 服务开发时,最重要的事情是从 XML 的角度进行考虑。这意味着 Java 语言概念不太重要。它是通过网络发送的 XML,您应该关注这一点。用于实现 Web 服务的 Java 是一个实现细节。
在本教程中,我们将定义一个由 Human Resources 部门创建的 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. 员工
该方案中还有 employee 的概念。下面是它在 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. HolidayRequest
元素和元素都可以放在 :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 定义这样的 Contract:
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 服务应该能够接受所有这些元素作为数据。这是不可取的:我们只想接受 .通过删除 wrapping element 标签(从而保留类型)并内联结果,我们可以完成此操作,如下所示:<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 有一个很好的内置类型,我们可以使用。我们还将 s 更改为 instances。最后,我们将 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"/> (1)
<xs:element name="Employee" type="hr:EmployeeType"/> (1)
</xs:all>
</xs:complexType>
</xs:element>
<xs:complexType name="HolidayType">
<xs:sequence>
<xs:element name="StartDate" type="xs:date"/> (2)
<xs:element name="EndDate" type="xs:date"/> (2)
</xs:sequence>
</xs:complexType>
<xs:complexType name="EmployeeType">
<xs:sequence>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:string"/> (3)
<xs:element name="LastName" type="xs:string"/> (3)
</xs:sequence>
</xs:complexType>
</xs:schema>
1 | all 告诉 XML 解析器,和 的顺序不重要。<Holiday/> <Employee/> |
2 | 我们使用数据类型(由年、月和日组成)和 。xs:date <StartDate/> <EndDate/> |
3 | xs:string 用于名字和姓氏。 |
我们将此文件存储为 .hr.xsd
3.3. 服务合同
服务协定通常表示为 WSDL 文件。请注意,在 Spring-WS 中,不需要手动编写 WSDL。基于 XSD 和一些约定,Spring-WS 可以为您创建 WSDL,如标题为“实现端点”的部分所述。本节的其余部分将介绍如何手动编写 WSDL。您可能希望跳到下一部分。
我们从标准序言开始 WSDL,并导入现有的 XSD。为了将架构与定义分开,我们为 WSDL 定义使用单独的命名空间:.以下清单显示了 preamble: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>
接下来,我们根据编写的架构类型添加消息。我们只有一条消息,即我们放入 schema 中的消息:<HolidayRequest/>
<wsdl:message name="HolidayRequest">
<wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>
</wsdl:message>
我们将消息作为操作添加到端口类型中:
<wsdl:portType name="HumanResource">
<wsdl:operation name="Holiday">
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>
</wsdl:operation>
</wsdl:portType>
该消息完成了 WSDL 的抽象部分(就像接口一样)并留下了具体部分。具体部分由 a (告诉客户端如何调用您刚刚定义的操作) 和 (告诉客户端在何处调用它) 组成。binding
service
添加混凝土部分是非常标准的。为此,请参阅您之前定义的抽象部分,确保用于元素( 已弃用),为操作选择 a(在本例中为 ,但任何 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" (1)
schemaLocation="hr.xsd"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="HolidayRequest"> (2)
<wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/> (3)
</wsdl:message>
<wsdl:portType name="HumanResource"> (4)
<wsdl:operation name="Holiday">
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/> (2)
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="HumanResourceBinding" type="tns:HumanResource"> (4)(5)
<soap:binding style="document" (6)
transport="http://schemas.xmlsoap.org/soap/http"/> (7)
<wsdl:operation name="Holiday">
<soap:operation soapAction="http://mycompany.com/RequestHoliday"/> (8)
<wsdl:input name="HolidayRequest">
<soap:body use="literal"/> (6)
</wsdl:input>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="HumanResourceService">
<wsdl:port binding="tns:HumanResourceBinding" name="HumanResourcePort"> (5)
<soap:address location="http://localhost:8080/holidayService/"/> (9)
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
1 | 我们导入 Data Contract 中定义的 schema。 |
2 | 我们定义消息,该消息在 .HolidayRequest portType |
3 | 类型在 schema 中定义。HolidayRequest |
4 | 我们定义端口类型,该类型在 .HumanResource binding |
5 | 我们定义绑定,它在 .HumanResourceBinding port |
6 | 我们使用 document/literal 样式。 |
7 | 文本表示 HTTP 传输。http://schemas.xmlsoap.org/soap/http |
8 | 该属性表示将随每个请求一起发送的 HTTP 标头。soapAction SOAPAction |
9 | 地址是可以调用 Web 服务的 URL。http://localhost:8080/holidayService/ |
前面的清单显示了最终的 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
上述命令将创建一个名为 .此目录中是一个目录,其中包含 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,例如 and 和 ,用于创建新的 Spring 容器。此文件的名称派生自附带的 servlet(在本例中为 )的名称,并附加了它。因此,如果定义 name 为 ,则特定于 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 消息
下面的清单显示了定义我们的 holiday 端点的类:
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 | 用 .这将该类标记为一种特殊类型的 ,适合在 Spring-WS 中处理 XML 消息,并且还使其有资格进行 适合组件扫描。HolidayEndpoint @Endpoint @Component |
2 | 这需要业务服务来运行,因此我们在构造函数中注入依赖项,并用 .
接下来,我们使用 JDOM2 API 设置 XPath 表达式。有四个表达式:用于提取文本值、用于提取结束日期和两个用于提取员工姓名的表达式。HolidayEndpoint HumanResourceService @Autowired //hr:StartDate <StartDate> //hr:EndDate |
3 | 该注释告诉 Spring-WS 该方法适合处理 XML 消息。此方法可以处理的消息类型由 annotation 值指示。在这种情况下,它可以
handle 具有 local 部分和 namespace 的 XML 元素。
有关将消息映射到终端节点的更多信息,请参阅下一节。@PayloadRoot handleHolidayRequest HolidayRequest http://mycompany.com/hr/schemas |
4 | 该方法是主要的处理方法,它从传入的 XML 消息中传递元素。该注释指示该参数应映射到
request 消息。我们使用 XPath 表达式从 XML 消息中提取字符串值,并使用 a(方法)将这些值转换为对象。使用这些值,我们在业务服务上调用一个方法。
通常,这会导致启动数据库事务并更改数据库中的某些记录。
最后,我们定义一个返回类型,它向 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 是因为它比编组技术更不脆弱。我们不需要严格的 schema 一致性,只要我们能找到日期和名称就行了。
因为我们使用 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 使用 Comments 驱动的端点,以及 element.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. 将消息路由到端点
作为编写端点的一部分,我们还使用了 annotation 来指示该方法可以处理哪些类型的消息。在 Spring-WS 中,此过程由 .在这里,我们使用 .下面的清单显示了我们之前使用的 Comments:@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. 提供服务和 Stub 实现
现在我们有了终端节点,我们需要及其实现以供 .下面的清单显示了该接口: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 (1)
public class StubHumanResourceService implements HumanResourceService {
public void bookHoliday(Date startDate, Date endDate, String name) {
System.out.println("Booking holiday for [" + startDate + "-" + endDate + "] for [" + name + "] ");
}
}
1 | 用 .这会将类标记为业务外观,这使得该类成为 in 的候选 INJECTION 对象。StubHumanResourceService @Service @Autowired HolidayEndpoint |
3.6. 发布 WSDL
最后,我们需要发布 WSDL。正如 服务契约 中所述,我们不需要自己编写 WSDL。Spring-WS 可以根据一些约定生成一个。以下是我们如何定义代:
<sws:dynamic-wsdl id="holiday" (1)
portTypeName="HumanResource" (3)
locationUri="/holidayService/" (4)
targetNamespace="http://mycompany.com/hr/definitions"> (5)
<sws:xsd location="/WEB-INF/hr.xsd"/> (2)
</sws:dynamic-wsdl>
1 | 确定可以检索 WSDL 的 URL。在这种情况下,is 表示可以检索 WSDL
就像在 Servlet 上下文中一样。完整的 URL 是 .id id holiday holiday.wsdl http://localhost:8080/holidayService/holiday.wsdl |
2 | 接下来,我们将 WSDL 端口类型设置为 .HumanResource |
3 | 我们设置可以访问服务的位置:。我们使用相对 URI,并指示框架对其进行转换
动态转换为绝对 URI。因此,如果服务部署到不同的上下文,我们不必手动更改 URI。
有关更多信息,请参阅名为“自动 WSDL 公开”的部分。要使位置转换正常工作,我们需要向 servlet 中添加一个 init 参数(如下一个清单所示)。/holidayService/ spring-ws web.xml |
4 | 我们为 WSDL 定义本身定义目标名称空间。不需要设置此属性。如果未设置,则 WSDL 与 XSD 架构具有相同的名称空间。 |
5 | 该元素引用了我们在 Data Contract 中定义的 Human Resource Schema。我们将 schema 放在应用程序的目录中。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 示例应用程序。之后,看一下 airline 示例,它稍微复杂一些,因为它使用了 JAXB、WS-Security、Hibernate 和事务服务层。最后,您可以阅读参考文档的其余部分。