前言
在当前面向服务架构的时代,越来越多的人使用网络服务连接之前未连接的系统。 最初,Web 服务被视为执行远程过程调用(RPC)的另一种方式。 然而,随着时间推移,人们发现RPC和Web服务之间存在很大区别。 尤其当与其他平台的互作性很重要时,通常最好发送包含处理请求所需所有数据的封装XML文档。 从概念上讲,基于XML的网络服务比消息队列优于远程解决方案。 总体而言,XML 应被视为数据的平台中立表示,是 SOA 的通用语言。 在开发或使用Web服务时,重点应放在这些XML,而非Java。
Spring-WS 专注于创建这些文档驱动的 Web 服务。 Spring-WS 促进了以契约为先的 SOAP 服务开发,允许通过多种作 XML 负载的方式之一创建灵活的 Web 服务。 Spring-WS 提供了一个强大的消息分发框架、一个与现有应用安全解决方案集成的 WS-Security 解决方案,以及一个遵循熟悉 Spring 模板模式的客户端 API。
一、引言
1. 什么是Spring Web Services?
1.1. 引言
Spring Web Services(Spring-WS)是 Spring 社区的产品,专注于创建文档驱动的 Web 服务。 Spring Web Services 旨在促进以合同为先的 SOAP 服务开发,通过多种作 XML 负载的方式之一,实现灵活的 Web 服务的创建。 该产品基于 Spring 本身,这意味着你可以将 Spring 的概念(如依赖注入)作为 Web 服务的组成部分使用。
人们使用Spring-WS有很多原因,但大多数人是在发现其他SOAP堆栈在遵循Web服务最佳实践方面缺乏后才被吸引。 春季-世界大学让最佳实践变得轻松。 这包括诸如WS-I基本配置文件、合同优先开发,以及合同与实现之间保持松散耦合等实践。 Spring-WS的其他主要功能包括:
1.1.4. 重用你的Spring专长
Spring-WS 在所有配置中都使用 Spring 应用上下文,这应该能帮助 Spring 开发者快速跟上。 此外,Spring-WS的架构与Spring-MVC相似。
1.2. 运行环境
Spring-WS 需要标准的 Java 17 运行环境。 Spring-WS 基于 Spring Framework 6.x 构建。
Spring-WS 由多个模块组成,将在本节剩余部分详细介绍。
-
XML模块(
Spring-XML)包含了Spring-WS的各种XML支持类。 该模块主要面向 Spring-WS 框架本身,而非面向 Web 服务开发者。 -
核心模块(
Spring-ws-核心)是 Spring Web 服务功能的核心部分。它提供核心功能WebServiceMessage和肥皂信息接口、服务器端框架(具备强大的消息分发功能)、实现Web服务端点的各种支持类,以及客户端WebService模板. -
支持模块(
Spring-WS-支持)包含额外的传输(JMS、电子邮件等)。 -
安全模块(
spring-ws-security)提供了与核心Web服务包集成的WS-Security实现。它允许你为SOAP消息添加主权Tokens,进行签名、解密和加密。此外,它还允许你使用现有的Spring Security实现进行身份验证和授权。
下图展示了 Spring-WS 模块之间的依赖关系。箭头表示依赖关系(即 Spring-WS Core 依赖于 Spring-XML 和 Spring OXM)。
2. 为什么先签合同?
在创建Web服务时,有两种开发风格:合同优先和合同优先。使用合同后处理方法时,你从Java代码开始,并从中生成Web服务合同(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.字符串. 正则表达式在转换过程中丢失,因为 Java 不支持这类扩展。
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服务时,通常会得到一个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 调用这项服务,我们很可能会使用java.time.localDateTime或java.time.Instant.
然而,这两个类别实际上描述的是时间,而非日期。
所以,我们实际上发送的数据代表了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
}
这是一个循环图:该图飞行指的是乘客,指的是飞行再。
像这样的循环图在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/编码)已被废弃,取而代之的是文档/字面格式(参见 WS-I 基本配置文件)。
这些只是处理O/X映射时存在的一些问题。 在编写网络服务时,尊重这些问题非常重要。 尊重它们的最佳方式是完全专注于XML,同时使用Java作为实现语言。 这就是合同优先的意义所在。
2.2. 合同优先与合同优先
除了前文提到的对象/XML 映射问题外,还有其他更倾向于以合同为先的开发风格的原因。
2.2.1. 脆弱性
如前所述,合同-最后的开发风格使得你的Web服务合同(WSDL和XSD)是从你的Java合同(通常是接口)生成的。 如果你采用这种方法,你无法保证合同会随着时间保持不变。 每次你更改Java合同并重新部署时,Web服务合同可能会有后续变化。
此外,并非所有 SOAP 栈都从 Java 合同生成相同的 Web 服务合同。 这意味着无论出于什么原因,将当前的SOAP栈更换为不同的栈,也可能改变你的网络服务合同。
当Web服务合同变更时,必须指示合同用户获取新合同,并可能修改代码以适应合同的任何变更。
为了让契约有用,它必须尽可能长时间保持恒定。 如果合同变更,你必须联系所有服务用户,指示他们获取新版本的合同。
2.2.2. 表演
当 Java 对象自动转换为 XML 时,无法确定传输了什么。 一个对象可能指代另一个对象,而另一个对象又指向另一个对象,依此类推。 最终,虚拟机堆中一半的对象可能会被转换成 XML,导致响应时间变慢。
使用先合同时,你会明确描述哪些 XML 被发送到哪里,确保它完全符合你的需求。
3. 编写以合同为先的网络服务
本教程将教你如何编写以契约为先的网络服务——也就是如何开发以XML架构或WSDL契约为先,再接Java代码的网络服务。 Spring-WS专注于这种开发风格,这个教程应该能帮你入门。 请注意,本教程的第一部分几乎没有Spring-WS的具体信息。 它主要涉及XML、XSD和WSDL。 第二部分则侧重于与 Spring-WS 的协议实现。
在进行合同优先的Web服务开发时,最重要的是用XML的角度思考。 这意味着 Java 语言的概念的重要性较低。 它是通过线路发送的 XML,你应该专注于它。 用 Java 实现 Web 服务是一个实现细节。
在本教程中,我们将定义由人力资源部门创建的网络服务。 客户可以向该服务发送假期申请表以预订假期。
3.1. 消息
本节重点介绍实际发送到和发送出网络服务的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>
我们使用了之前相同的命名空间。
如果是这样<员工/>元素可以在其他场景中使用,使用不同的命名空间可能更合理,例如http://example.com/employees/schemas.
3.1.3. 假期请求
两者假期元素和员工元素可以被放入<假期请求/>:
<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>
这两个元素的顺序无关紧要:<员工/>可能是第一个元素。
重要的是所有数据都在那里。
事实上,数据才是唯一重要的:我们采取数据驱动的方法。
3.2. 数据合同
既然我们已经看到了一些可用的XML数据示例,将它形式化成一个模式就很有意义了。 该数据合同定义了我们接受的消息格式。 定义此类XML契约有四种不同的方式:
DTD 的命名空间支持有限,因此不适合 Web 服务。 Relax NG 和 Schematron 比 XML Schema 简单。 遗憾的是,这些平台在各平台上的支持并不普遍。 因此,我们使用XML Schema。
迄今为止,创建XSD最简单的方法是从样本文档推断出来。 任何好的XML编辑器或Java IDE都支持此功能。 基本上,这些工具使用一些示例XML文档生成一个验证所有文件的模式。 最终效果当然需要打磨,但这是一个很好的起点。
利用之前描述的示例,我们得到以下生成的模式:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/hr/schemas"
xmlns:hr="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Holiday"/>
<xs:element ref="hr:Employee"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Holiday">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:StartDate"/>
<xs:element ref="hr:EndDate"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="StartDate" type="xs:NMTOKEN"/>
<xs:element name="EndDate" type="xs:NMTOKEN"/>
<xs:element name="Employee">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Number"/>
<xs:element ref="hr:FirstName"/>
<xs:element ref="hr:LastName"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:NCName"/>
<xs:element name="LastName" type="xs:NCName"/>
</xs:schema>
生成的模式可以被改进。
首先要注意的是,每个类型都有一个根级元素声明。
这意味着网络服务应能够将所有这些元素视为数据。
这并不理想:我们只想接受<假期请求/>.
通过去除包裹元素标签(从而保留类型)并内联结果,我们可以实现以下效果:
<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 内置了优秀的日期我们能用的类型。
我们也更改NCNames 到字符串实例。
最后,我们更改序列在<假期请求/>自都.
这告诉XML解析器 的顺序<节日/>和<员工/>不重要。
我们最终的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 | 都告诉XML解析器 的顺序<节日/>和<员工/>不重要。 |
| 2 | 我们使用xs:日期数据类型(包括年、月和一天)<开始日期/>和<结束日期/>. |
| 3 | xs:字符串用“名字”表示名字和姓氏。 |
我们将此文件存储为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>
接下来,我们根据书面模式类型添加消息。
我们只有一条消息,是<假期请求/>我们输入了模式:
<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的抽象部分(可以说是接口),并留下具体部分。
具体部分由一个捆绑(告诉客户端如何调用你刚刚定义的作)以及一个服务(这告诉客户端在哪里调用它)。
加混凝土部分是很常见的。
要做到这一点,请参考你之前定义的抽象部分,确保使用文档/字面对于肥皂:装订元素(RPC/编码已废弃),选择肥皂剧行动对于运算(此例中,http://mycompany.com/RequestHoliday,但任何URI都成立),并确定位置你希望请求到达的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 | 我们定义假期请求消息,该消息被用于端口类型. |
| 3 | 这假期请求类型定义在模式中。 |
| 4 | 我们定义人力资源端口类型,用于捆绑. |
| 5 | 我们定义人力资源绑定装订,这种方式被用于端口. |
| 6 | 我们采用文档/字面格式。 |
| 7 | 字面意思http://schemas.xmlsoap.org/soap/http表示 HTTP 传输。 |
| 8 | 这肥皂剧行动属性表示肥皂剧HTTP头,每次请求都会发送。 |
| 9 | 这http://localhost:8080/holidayService/address 是可以调用该网络服务的 URL。 |
上文列表显示了最终的 WSDL。 我们将在下一节描述如何实现最终的模式和WSDL。
3.4. 创建项目
在本节中,我们使用 Maven 为我们创建初始项目结构。 虽然不是必须的,但大大减少了我们为设置HolidayService编写的代码量。
以下命令通过使用 Spring-WS 原型(即项目模板)为我们创建一个 Maven 网页应用项目:
mvn archetype:create -DarchetypeGroupId=org.springframework.ws \ -DarchetypeArtifactId=spring-ws-archetype \ -DarchetypeVersion= \ -DgroupId=com.mycompany.hr \ -DartifactId=holidayService
前一个命令创建一个新的目录,称为假期服务.
该目录中有一个src/main/webapp目录,包含WAR文件的根。
你可以找到标准的网页应用部署描述符(网络基础设施/web.xml)定义了Spring-WSMessageDispatcherServlet并将所有入请求映射到该servlet:
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>MyCompany HR Holiday Service</display-name>
<!-- take special notice of the name of this servlet -->
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
除了前面的网络基础设施/web.xml文件,你还需要另一个名为 Spring-WS 的配置文件网络输入/spring-ws-servlet.xml.
该文件包含所有 Spring-WS 专属的豆子,例如端点和WebServiceMessageReceivers并用于制作新的春季容器。
该文件的名称来源于随从服务组的名称(此处指)“春-西”)-servlet.xml附加在上面。
所以如果你定义一个MessageDispatcherServlet与名称一起炸药,Spring-WS 专用配置文件的名称变为网络信息/dynamite-servlet.xml.
一旦你建立了项目结构,就可以把上一节的模式和WSDL放进去网络-INF/文件夹。
3.5. 端点的实现
在 Spring-WS 中,你实现端点来处理收到的 XML 消息。
端点通常通过用@Endpoint注解。
在这个端点类中,你可以创建一个或多个处理来电请求的方法。
方法签名可以相当灵活。
你可以包含几乎任何与收到的XML消息相关的参数类型,正如我们本章后面会解释的那样。
3.5.1. 处理XML消息
以下列表显示了定义我们假日端点的类:
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 | 这假期终点注释为@Endpoint.
这标志着该类作为一种特殊的@Component适用于 Spring-WS 中的 XML 消息,同时也符合组件扫描的资格。 |
| 2 | 这假期终点需要人力资源服务业务服务以运行,因此我们将依赖注入构造子并注释为@Autowired.
接下来,我们通过使用 JDOM2 API 设置了 XPath 表达式。
有四个表达:hr:开始日期用于提取<开始日期>文本值,hr:结束日期用于提取结束日期,两个用于提取员工姓名。 |
| 3 | 这@PayloadRoot注释告诉Spring-WShandleHolidayRequest方法适用于处理XML消息。
该方法能处理的消息类型由注释值指示。
在这种情况下,它可以处理具有假期请求地方部分及http://mycompany.com/hr/schemasNamespace。
关于将消息映射到端点的更多信息将在下一节提供。 |
| 4 | 这handleHolidayRequest(..)方法 是主要的处理方法,该方法传递给<假期请求/>来自输入XML消息的元素。
这@RequestPayload注释表明假期请求参数应映射到请求消息的有效载荷。
我们使用 XPath 表达式从 XML 消息中提取字符串值,并将其转换为日期通过使用 aSimpleDate格式(parseData方法)。
基于这些数值,我们在业务服务中调用一种方法。
通常,这会导致数据库事务被启动,并对数据库中的某些记录进行修改。
最后,我们定义一个无效返回类型,向Spring-WS表明我们不想发送响应消息。
如果我们需要响应消息,可以返回一个JDOM元素来表示响应消息的有效载荷。 |
使用 JDOM 只是处理 XML 的众多选项之一。 其他选项包括DOM、dom4j、XOM、SAX和StAX,还有如JAXB、Castor、XMLBeans、JiBX和XStream等编组技术,详见下一章。 我们选择JDOM是因为它能访问原始XML,而且它是基于类(而非像W3C DOM和dom4j那样的接口和工厂方法),这使得代码不那么冗长。 我们使用XPath是因为它比编组技术更不脆弱。 只要能找到日期和名称,我们不需要严格的模式一致性。
因为我们使用 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通过分量扫描生成XML配置文件。
我们还指示 Spring-WS 使用注释驱动的端点,且<sws:注释驱动>元素。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:sws="http://www.springframework.org/schema/web-services"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.mycompany.hr"/>
<sws:annotation-driven/>
</beans>
3.5.2. 将消息路由到端点
在编写端点时,我们还使用了@PayloadRoot注释以表示哪些类型的消息可以由handleHolidayRequest方法。
在春季-WS中,这一过程由端点映射.
在这里,我们根据内容通过PayloadRootAnnotationMethodEndpointMapping.
以下列表展示了我们之前使用的注释:
@PayloadRoot(namespace = "http://mycompany.com/hr/schemas", localPart = "HolidayRequest")
前例所示的注释基本上意味着,每当收到带有命名空间的XML消息时http://mycompany.com/hr/schemas以及假期请求本地名称,它被路由到handleHolidayRequest方法。
通过使用<sws:注释驱动>在我们的配置中,我们启用了对 的检测@PayloadRoot附注。
在一个端点中可能(且相当常见)存在多个相关的处理方法,每个处理不同的XML消息。
还有其他将端点映射到XML消息的方法,相关内容将在下一章中详细说明。
3.5.3. 提供服务和存根实现
既然我们有了端点,我们需要人力资源服务以及用于以下用途的实现假期终点.
以下列表显示了人力资源服务接口:
package com.mycompany.hr.service;
public interface HumanResourceService {
void bookHoliday(Date startDate, Date endDate, String name);
}
出于教程目的,我们使用一个简单的存根实现人力资源服务:
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.
这标志着该类别作为商业外观,因此有可能被@Autowired在假期终点. |
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。
在这种情况下,身份证是假期,这意味着WSDL可以检索为假期.WSDL在 servlet 语境下。
完整网址为http://localhost:8080/holidayService/holiday.wsdl. |
| 2 | 接下来,我们将WSDL端口类型设置为人力资源. |
| 3 | 我们设置了服务可达的位置:/holidayService/.
我们使用一个相对URI,并指示框架动态将其转换为绝对URI。
因此,如果服务部署到不同上下文,我们无需手动更改URI。
更多信息请参见“自动WSDL暴露”部分。
为了让位置变换正常工作,我们需要添加一个初始化参数春-Wsservlet 在web.xml(见下一条列表)。 |
| 4 | 我们为WSDL定义本身定义了目标命名空间。 设置该属性并非必需。 如果未设置,WSDL与XSD模式具有相同的命名空间。 |
| 5 | 这XSD元素指的是我们在数据合同中定义的人力资源模式。
我们把图式放在网内应用程序目录。 |
以下列表展示了如何添加初始化参数:
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
你可以通过以下方式创建WAR文件MVN安装.
如果你部署应用程序(比如Tomcat、Jetty等),并将浏览器指向该位置,你会看到生成的WSDL。
该WSDL已可供客户端使用,如soapUI或其他SOAP框架。
本教程到此结束。 教程代码可以在 Spring-WS 的完整发行版中找到。 如果你想继续,可以看看分布中包含的回声样本应用。 之后,看看航空公司的样本,它更复杂,因为它使用 JAXB、WS-Security、Hibernate 和交易服务层。 最后,你可以阅读其他参考文献。