前言

在当前面向服务架构的时代,越来越多的人使用网络服务连接之前未连接的系统。 最初,Web 服务被视为执行远程过程调用(RPC)的另一种方式。 然而,随着时间推移,人们发现RPC和Web服务之间存在很大区别。 尤其当与其他平台的互作性很重要时,通常最好发送包含处理请求所需所有数据的封装XML文档。 从概念上讲,基于XML的网络服务比消息队列优于远程解决方案。 总体而言,XML 应被视为数据的平台中立表示,是 SOA 的通用语言。 在开发或使用Web服务时,重点应放在这些XML,而非Java。spring-doc.cadn.net.cn

Spring-WS 专注于创建这些文档驱动的 Web 服务。Spring-WS 促进了以合同为先的 SOAP 服务开发,允许通过多种作 XML 负载的方式之一创建灵活的 Web 服务。Spring-WS 提供了一个强大的消息调度框架,一个与现有应用安全解决方案集成的 WS-Security 解决方案,以及一个遵循熟悉 Spring 模板模式的客户端 APIspring-doc.cadn.net.cn

一、引言

参考文档的第一部分概述了 Spring Web Services 及其底层概念。随后,解释了合同优先的 Web 服务开发背后的概念。最后,第三部分提供了教程

1. 什么是Spring Web Services?

1.1. 引言

Spring Web Services(Spring-WS)是 Spring 社区的产品,专注于创建文档驱动的 Web 服务。Spring Web Services 旨在促进以合同为先的 SOAP 服务开发,允许通过多种作 XML 负载的方式创建灵活的 Web 服务。该产品基于 Spring 本身,这意味着你可以将 Spring 的概念(如依赖注入)作为 Web 服务的组成部分使用。spring-doc.cadn.net.cn

人们使用Spring-WS有很多原因,但大多数人是在发现其他SOAP堆栈在遵循Web服务最佳实践方面缺乏后才被吸引。Spring-WS使最佳实践变得简单易行。这包括诸如WS-I基本配置文件、合同优先开发,以及合同与实现之间的松耦合等实践。Spring-WS的其他关键特性包括:spring-doc.cadn.net.cn

1.1.1. 强映射

你可以根据消息负载、SOAP 动作头或 XPath 表达式,将收到的 XML 请求分发到任何对象。spring-doc.cadn.net.cn

1.1.2. XML API 支持

收到的XML消息不仅可以用标准JAXP API(如DOM、SAX和StAX)处理,还可以用JDOM、dom4j、XOM,甚至编组技术处理。spring-doc.cadn.net.cn

1.1.3. 灵活的XML编组

Spring-WS 基于 Spring 框架中的对象/XML 映射模块构建,支持 JAXB 1 和 2、Castor、XMLBeans、JiBX 和 XStream。spring-doc.cadn.net.cn

1.1.4. 重用你的Spring专长

Spring-WS 在所有配置中都使用 Spring 应用上下文,这有助于 Spring 开发者快速跟上。此外,Spring-WS 的架构类似于 Spring-MVC。spring-doc.cadn.net.cn

1.1.5. WS-Security 支持

WS-Security 允许你签署 SOAP 消息,对它们进行加密和解密,或对其进行身份验证。spring-doc.cadn.net.cn

1.1.6. 与 Spring Security 的集成

Spring-WS 的 WS-Security 实现支持与 Spring Security 集成。这意味着你也可以使用现有的 Spring Security 配置来运行 SOAP 服务。spring-doc.cadn.net.cn

1.1.7. Apache 许可证

你可以放心地在项目中使用Spring-WS。spring-doc.cadn.net.cn

1.2. 运行环境

Spring-WS 需要标准的 Java 17 运行时环境。Spring-WS 基于 Spring Framework 6.x 构建。spring-doc.cadn.net.cn

Spring-WS 由多个模块组成,将在本节剩余部分详细介绍。spring-doc.cadn.net.cn

  • XML模块(Spring-XML包含多种 Spring-WS 的 XML 支持类。该模块主要面向 Spring-WS 框架本身,而非 Web 服务开发者。spring-doc.cadn.net.cn

  • 核心模块(Spring-ws-核心)是Spring网络服务功能的核心部分。 它提供了中心WebServiceMessage肥皂信息接口、服务器端框架(具备强大的消息分发功能)、实现Web服务端点的各种支持类,以及客户端 WebService模板.spring-doc.cadn.net.cn

  • 支持模块(Spring-WS-支持)包含额外的传输(JMS、电子邮件等)。spring-doc.cadn.net.cn

  • 安全模块(spring-ws-security)提供与核心Web服务包集成的WS-Security实现。 它允许你签名、解密和加密,并为SOAP消息添加主Tokens。 此外,它还允许你使用现有的 Spring Security 实现进行身份验证和授权。spring-doc.cadn.net.cn

下图展示了 Spring-WS 模块之间的依赖关系。 箭头表示依赖关系(即 Spring-WS Core 依赖于 Spring-XML 和 Spring OXM)。spring-doc.cadn.net.cn

春季季

1.3. 支持标准

Spring-WS 支持以下标准:spring-doc.cadn.net.cn

2. 为什么先签合同?

在创建网络服务时,有两种开发风格:合同优先和合同优先。 当你使用最后契约方法时,你会从Java代码开始,然后让Web服务合同(WSDL格式——见侧边栏)从中生成。 使用先契约时,你从WSDL契约开始,再用Java实现契约。spring-doc.cadn.net.cn

什么是WSDL?

WSDL 代表 Web Service Description Language。 WSDL 文件是一种描述网络服务的 XML 文档。 它指定了服务的位置以及服务所暴露的作(或方法)。 有关WSDL的更多信息,请参见WSDL规范spring-doc.cadn.net.cn

Spring-WS 仅支持以合同优先的开发风格,本节将解释原因。spring-doc.cadn.net.cn

2.1. 对象/XML阻抗不匹配

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

本节大部分内容受[alpine][effective-enterprise-java]启发。

2.1.1. XSD 扩展

在 Java 中,改变类行为的唯一方法是将其子类子类,从而将新的行为添加到该子类中。 在 XSD 中,你可以通过限制数据类型来扩展——也就是限制元素和属性的有效值。 例如,考虑以下例子:spring-doc.cadn.net.cn

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

这种类型通过正则表达式限制 XSD 字符串,只允许三个大写字母。 如果将该类型转换为 Java,我们最终得到一个普通的java.lang.字符串. 正则表达式在转换过程中丢失,因为 Java 不支持这类扩展。spring-doc.cadn.net.cn

2.1.2. 不可携带类型

网络服务最重要的目标之一是实现互作性:支持多个平台,如Java、.NET、Python等。 由于这些语言都有不同的类库,你必须使用某种通用的跨语言格式来在它们之间交流。 这种格式是XML,所有这些语言都支持它。spring-doc.cadn.net.cn

因此,你必须确保在服务实施中使用可移动类型。 举例来说,考虑一个返回java.util.TreeMap:spring-doc.cadn.net.cn

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(系统.集合.哈希表),其语义不同。spring-doc.cadn.net.cn

这个问题在客户端工作时也存在。 请考虑以下XSD摘要,描述一项服务合同:spring-doc.cadn.net.cn

<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.localDateTimejava.time.Instant. 然而,这两个类别实际上描述的是时间,而非日期。 所以,我们实际上发送的数据代表了2007年4月4日午夜(2007-04-04T00:00:00),这与2007-04-04.spring-doc.cadn.net.cn

2.1.3. 循环图

假设我们有以下类结构:spring-doc.cadn.net.cn

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,结果会是这样的:spring-doc.cadn.net.cn

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

处理此类结构可能需要较长时间,因为该循环没有停止条件。spring-doc.cadn.net.cn

解决这个问题的一种方法是使用已编组对象的引用:spring-doc.cadn.net.cn

<flight number="KL1117">
  <passengers>
    <passenger>
      <name>Arjen Poutsma</name>
      <flight href="KL1117" />
    </passenger>
    ...
  </passengers>
</flight>

这解决了递归问题,但引入了新的问题。 首先,你不能用XML验证器来验证这个结构。 另一个问题是,SOAP 中使用这些引用的标准方式(RPC/编码)已被废弃,取而代之的是文档/字面格式(参见 WS-I 基本配置文件)。spring-doc.cadn.net.cn

这些只是处理O/X映射时存在的一些问题。 在编写网络服务时,尊重这些问题非常重要。 尊重它们的最佳方式是完全专注于XML,同时使用Java作为实现语言。 这就是合同优先的意义所在。spring-doc.cadn.net.cn

2.2. 合同优先与合同优先

除了前文提到的对象/XML 映射问题外,还有其他更倾向于以合同为先的开发风格的原因。spring-doc.cadn.net.cn

2.2.1. 脆弱性

如前所述,合同-最后的开发风格使得你的Web服务合同(WSDL和XSD)是从你的Java合同(通常是接口)生成的。 如果你采用这种方法,你无法保证合同会随着时间保持不变。 每次你更改Java合同并重新部署时,Web服务合同可能会有后续变化。spring-doc.cadn.net.cn

此外,并非所有 SOAP 栈都从 Java 合同生成相同的 Web 服务合同。 这意味着无论出于什么原因,将当前的SOAP栈更换为不同的栈,也可能改变你的网络服务合同。spring-doc.cadn.net.cn

当Web服务合同变更时,必须指示合同用户获取新合同,并可能修改代码以适应合同的任何变更。spring-doc.cadn.net.cn

为了让契约有用,它必须尽可能长时间保持恒定。 如果合同变更,你必须联系所有服务用户,指示他们获取新版本的合同。spring-doc.cadn.net.cn

2.2.2. 表演

当 Java 对象自动转换为 XML 时,无法确定传输了什么。 一个对象可能指代另一个对象,而另一个对象又指向另一个对象,依此类推。 最终,虚拟机堆中一半的对象可能会被转换成 XML,导致响应时间变慢。spring-doc.cadn.net.cn

使用先合同时,你会明确描述哪些 XML 被发送到哪里,确保它完全符合你的需求。spring-doc.cadn.net.cn

2.2.3. 可重复使用性

在一个独立文件中定义你的模式可以在不同场景中重复使用该文件。 考虑 的定义机场代码在一个名为Airline.XSD:spring-doc.cadn.net.cn

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

你可以在其他模式中,甚至 WSDL 文件中重复使用这个定义,方法是使用进口陈述。spring-doc.cadn.net.cn

2.2.4. 版本管理

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

如果采用先契约,合同与实现之间的耦合可以更松散。 这种松散耦合使我们在一个类别中实现了两个版本的契约。 例如,我们可以用XSLT样式表将任何“旧样式”消息转换为“新样式”消息。spring-doc.cadn.net.cn

3. 编写以合同为先的网络服务

本教程将教你如何编写以契约为先的网络服务——也就是如何开发以XML架构或WSDL契约为先,再接Java代码的网络服务。 Spring-WS专注于这种开发风格,这个教程应该能帮你入门。 请注意,本教程的第一部分几乎没有Spring-WS的具体信息。 它主要涉及XML、XSD和WSDL。 第二部分则侧重于与 Spring-WS 的协议实现。spring-doc.cadn.net.cn

在进行合同优先的Web服务开发时,最重要的是用XML的角度思考。 这意味着 Java 语言的概念的重要性较低。 它是通过线路发送的 XML,你应该专注于它。 用 Java 实现 Web 服务是一个实现细节。spring-doc.cadn.net.cn

在本教程中,我们将定义由人力资源部门创建的网络服务。 客户可以向该服务发送假期申请表以预订假期。spring-doc.cadn.net.cn

3.1. 消息

本节重点介绍实际发送到和发送出网络服务的XML消息。 我们首先确定这些信息的样子。spring-doc.cadn.net.cn

3.1.1. 假期

在这种情况下,我们需要处理假期请求,因此确定假期在XML中的样子是合理的:spring-doc.cadn.net.cn

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

假期包括一个开始日期和一个结束日期。 我们还决定使用标准的ISO 8601日期格式,因为这样可以省去大量解析麻烦。 我们还为元素添加了命名空间,以确保我们的元素可以在其他XML文档中使用。spring-doc.cadn.net.cn

3.1.2. 员工

情景中还有员工的概念。 以下是它在XML中的样子:spring-doc.cadn.net.cn

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

我们使用了之前相同的命名空间。 如果是这样<员工/>元素可以在其他场景中使用,使用不同的命名空间可能更合理,例如http://example.com/employees/schemas.spring-doc.cadn.net.cn

3.1.3. 假期请求

两者假期元素和员工元素可以被放入<假期请求/>:spring-doc.cadn.net.cn

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

这两个元素的顺序无关紧要:<员工/>可能是第一个元素。 重要的是所有数据都在那里。 事实上,数据才是唯一重要的:我们采取数据驱动的方法。spring-doc.cadn.net.cn

3.2. 数据合同

既然我们已经看到了一些可用的XML数据示例,将它形式化成一个模式就很有意义了。 该数据合同定义了我们接受的消息格式。 定义此类XML契约有四种不同的方式:spring-doc.cadn.net.cn

DTD 的命名空间支持有限,因此不适合 Web 服务。 Relax NG 和 Schematron 比 XML Schema 简单。 遗憾的是,这些平台在各平台上的支持并不普遍。 因此,我们使用XML Schema。spring-doc.cadn.net.cn

迄今为止,创建XSD最简单的方法是从样本文档推断出来。 任何好的XML编辑器或Java IDE都支持此功能。 基本上,这些工具使用一些示例XML文档生成一个验证所有文件的模式。 最终效果当然需要打磨,但这是一个很好的起点。spring-doc.cadn.net.cn

利用之前描述的示例,我们得到以下生成的模式:spring-doc.cadn.net.cn

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

生成的模式可以被改进。 首先要注意的是,每个类型都有一个根级元素声明。 这意味着网络服务应能够将所有这些元素视为数据。 这并不理想:我们只想接受<假期请求/>. 通过去除包裹元素标签(从而保留类型)并内联结果,我们可以实现以下效果:spring-doc.cadn.net.cn

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

该模式仍有一个问题:对于这样的模式,你可以预期以下信息会被验证:spring-doc.cadn.net.cn

<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现在看起来如下:spring-doc.cadn.net.cn

<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.spring-doc.cadn.net.cn

3.3. 服务合同

服务合同通常以WSDL文件表示。 请注意,在Spring-WS中,WSDL不需要手动编写。 基于XSD和一些惯例,Spring-WS可以为你创建WSDL,详见“实现端点”部分。 本节剩余部分将展示如何手工编写WSDL。 你可能想跳到下一部分spring-doc.cadn.net.cn

我们以标准前言开始WSDL并导入现有的XSD。 为了将模式与定义分离,我们为WSDL定义使用独立的命名空间:http://mycompany.com/hr/definitions. 以下列表展示了序言:spring-doc.cadn.net.cn

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

接下来,我们根据书面模式类型添加消息。 我们只有一条消息,是<假期请求/>我们输入了模式:spring-doc.cadn.net.cn

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

我们将消息添加到端口类型中作为作:spring-doc.cadn.net.cn

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

该消息结束了WSDL的抽象部分(可以说是接口),并留下具体部分。 具体部分由一个捆绑(告诉客户端如何调用你刚刚定义的作)以及一个服务(这告诉客户端在哪里调用它)。spring-doc.cadn.net.cn

加混凝土部分是很常见的。 要做到这一点,请参考你之前定义的抽象部分,确保使用文档/字面对于肥皂:装订元素(RPC/编码已废弃),选择肥皂剧行动对于运算(此例中,http://mycompany.com/RequestHoliday,但任何URI都成立),并确定位置你希望请求到达的URL(在这里,http://mycompany.com/humanresources):spring-doc.cadn.net.cn

<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。spring-doc.cadn.net.cn

3.4. 创建项目

在本节中,我们使用 Maven 为我们创建初始项目结构。 虽然不是必须的,但大大减少了我们为设置HolidayService编写的代码量。spring-doc.cadn.net.cn

以下命令通过使用 Spring-WS 原型(即项目模板)为我们创建一个 Maven 网页应用项目:spring-doc.cadn.net.cn

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:spring-doc.cadn.net.cn

<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.spring-doc.cadn.net.cn

一旦你建立了项目结构,就可以把上一节的模式和WSDL放进去网络-INF/文件夹。spring-doc.cadn.net.cn

3.5. 端点的实现

在 Spring-WS 中,你实现端点来处理收到的 XML 消息。 端点通常通过用@Endpoint注解。 在这个端点类中,你可以创建一个或多个处理来电请求的方法。 方法签名可以相当灵活。 你可以包含几乎任何与收到的XML消息相关的参数类型,正如我们本章后面会解释的那样。spring-doc.cadn.net.cn

3.5.1. 处理XML消息

在这个示例应用中,我们使用 JDom 2 来处理 XML 消息。 我们还使用 XPath,因为它允许我们选择 XML JDOM 树的特定部分,而无需严格的模式一致性。spring-doc.cadn.net.cn

以下列表显示了定义我们假日端点的类:spring-doc.cadn.net.cn

package com.mycompany.hr.ws;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;

import com.mycompany.hr.service.HumanResourceService;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;

@Endpoint                                                                                     (1)
public class HolidayEndpoint {

    private static final String NAMESPACE_URI = "http://mycompany.com/hr/schemas";

    private XPathExpression<Element> startDateExpression;

    private XPathExpression<Element> endDateExpression;

    private XPathExpression<Element> firstNameExpression;

    private XPathExpression<Element> lastNameExpression;

    private HumanResourceService humanResourceService;

    @Autowired                                                                                (2)
    public HolidayEndpoint(HumanResourceService humanResourceService) throws JDOMException {
        this.humanResourceService = humanResourceService;

        Namespace namespace = Namespace.getNamespace("hr", NAMESPACE_URI);
        XPathFactory xPathFactory = XPathFactory.instance();
        startDateExpression = xPathFactory.compile("//hr:StartDate", Filters.element(), null, namespace);
        endDateExpression = xPathFactory.compile("//hr:EndDate", Filters.element(), null, namespace);
        firstNameExpression = xPathFactory.compile("//hr:FirstName", Filters.element(), null, namespace);
        lastNameExpression = xPathFactory.compile("//hr:LastName", Filters.element(), null, namespace);
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "HolidayRequest")                      (3)
    public void handleHolidayRequest(@RequestPayload Element holidayRequest) throws Exception {(4)
        Date startDate = parseDate(startDateExpression, holidayRequest);
        Date endDate = parseDate(endDateExpression, holidayRequest);
        String name = firstNameExpression.evaluateFirst(holidayRequest).getText() + " " + lastNameExpression.evaluateFirst(holidayRequest).getText();

        humanResourceService.bookHoliday(startDate, endDate, name);
    }

    private Date parseDate(XPathExpression<Element> expression, Element element) throws ParseException {
        Element result = expression.evaluateFirst(element);
        if (result != null) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            return dateFormat.parse(result.getText());
        } else {
            throw new IllegalArgumentException("Could not evaluate [" + expression + "] on [" + element + "]");
        }
    }

}
1 假期终点注释为@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是因为它比编组技术更不脆弱。 只要能找到日期和名称,我们不需要严格的模式一致性。spring-doc.cadn.net.cn

因为我们使用 JDOM,必须在 Maven 中添加一些依赖关系pom.xml,该邮件位于我们项目目录的根节点。 以下是POM的相关部分:spring-doc.cadn.net.cn

<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:注释驱动>元素。spring-doc.cadn.net.cn

<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. 以下列表展示了我们之前使用的注释:spring-doc.cadn.net.cn

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

前例所示的注释基本上意味着,每当收到带有命名空间的XML消息时http://mycompany.com/hr/schemas以及假期请求本地名称,它被路由到handleHolidayRequest方法。 通过使用<sws:注释驱动>在我们的配置中,我们启用了对 的检测@PayloadRoot附注。 在一个端点中可能(且相当常见)存在多个相关的处理方法,每个处理不同的XML消息。spring-doc.cadn.net.cn

还有其他将端点映射到XML消息的方法,相关内容将在下一章中详细说明。spring-doc.cadn.net.cn

3.5.3. 提供服务和存根实现

既然我们有了端点,我们需要人力资源服务以及用于以下用途的实现假期终点. 以下列表显示了人力资源服务接口:spring-doc.cadn.net.cn

package com.mycompany.hr.service;

import java.util.Date;

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

出于教程目的,我们使用一个简单的存根实现人力资源服务:spring-doc.cadn.net.cn

package com.mycompany.hr.service;

import java.util.Date;

import org.springframework.stereotype.Service;

@Service                                                                 (1)
public class StubHumanResourceService implements HumanResourceService {
    public void bookHoliday(Date startDate, Date endDate, String name) {
        System.out.println("Booking holiday for [" + startDate + "-" + endDate + "] for [" + name + "] ");
    }
}
1 StubHumanResourceService注释为@Service. 这标志着该类别作为商业外观,因此有可能被@Autowired假期终点.

3.6. WSDL的出版

最后,我们需要发布WSDL文件。 正如服务合同中所述,我们无需自行编写WSDL。 Spring-WS可以根据某些约定生成一个。 我们如何定义“世代”:spring-doc.cadn.net.cn

<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元素指的是我们在数据合同中定义的人力资源模式。 我们把图式放在网内应用程序目录。

以下列表展示了如何添加初始化参数:spring-doc.cadn.net.cn

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

你可以通过以下方式创建WAR文件MVN安装. 如果你部署应用程序(比如Tomcat、Jetty等),并将浏览器指向该位置,你会看到生成的WSDL。 该WSDL已可供客户端使用,如soapUI或其他SOAP框架。spring-doc.cadn.net.cn

本教程到此结束。 教程代码可以在 Spring-WS 的完整发行版中找到。 如果你想继续,可以看看分布中包含的回声样本应用。 之后,看看航空公司的样本,它更复杂,因为它使用 JAXB、WS-Security、Hibernate 和交易服务层。 最后,你可以阅读其他参考文献。spring-doc.cadn.net.cn