第 3 章. 编写契约优先的 Web 服务

3.1. 引言

本教程将向您展示如何编写契约优先的 Web 服务,也就是说,首先使用 XML Schema/WSDL 契约,然后编写 Java 代码来开发 Web 服务。Spring-WS 专注于这种开发风格,本教程将帮助您入门。请注意,本教程的第一部分几乎不包含 Spring-WS 特有的信息:它主要关于 XML、XSD 和 WSDL。第二部分重点介绍如何使用 Spring-WS 实现此契约。

在进行契约优先的 Web 服务开发时,最重要的事情是尝试用 XML 的方式思考。这意味着 Java 语言的概念变得不那么重要。通过网络传输的是 XML,您应该专注于此。使用 Java 来实现 Web 服务是一个实现细节。一个重要的细节,但终究是一个细节。

在本教程中,我们将定义一个由人力资源部门创建的 Web 服务。客户端可以向该服务发送请假申请表来预订假期。

3.2. 消息

在本节中,我们将重点介绍发送到 Web 服务和从 Web 服务发送的实际 XML 消息。我们将首先确定这些消息的样子。

3.2.1. 假期

在此场景中,我们需要处理请假申请,因此确定假期在 XML 中的样子很有意义

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

假期包括开始日期和结束日期。我们还决定对日期使用标准的ISO 8601日期格式,因为这将省去很多解析麻烦。我们还为元素添加了命名空间,以确保我们的元素可以在其他 XML 文档中使用。

3.2.2. 员工

在此场景中,还涉及到员工的概念。以下是它在 XML 中的样子

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

我们使用了与之前相同的命名空间。如果此 <Employee/> 元素可在其他场景中使用,则使用不同的命名空间(例如 http://mycompany.com/employees/schemas)可能更有意义。

3.2.3. 请假申请

假期和员工元素都可以放在一个 <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.3. 数据契约

现在我们已经看到了一些将要使用的 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>

这个生成的模式显然可以改进。首先要注意的是,每个类型都有一个根级别的元素声明。这意味着 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>
    <!-- ... -->
</HolidayRequest>

显然,我们必须确保开始日期和结束日期确实是日期。XML Schema 有一个出色的内置 date 类型,我们可以使用。我们还将 NCName 更改为 string。最后,我们将 <HolidayRequest/> 中的 sequence 更改为 all。这告诉 XML 解析器,<Holiday/><Employee/> 的顺序不重要。我们最终的 XSD 现在看起来像这样

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:hr="http://mycompany.com/hr/schemas"
        elementFormDefault="qualified"
        targetNamespace="http://mycompany.com/hr/schemas">
    <xs:element name="HolidayRequest">
        <xs:complexType>
            <xs:all>
                <xs:element name="Holiday" type="hr:HolidayType"/>                       (1)
                <xs:element name="Employee" type="hr:EmployeeType"/>
            </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"/>                                  (2)
        </xs:sequence>                                                                   (2)
    </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"/>                               (3)
        </xs:sequence>                                                                   (3)
    </xs:complexType>
</xs:schema>

1

all 告诉 XML 解析器,<Holiday/><Employee/> 的顺序不重要。

2

我们对 <StartDate/><EndDate/> 使用 xsd:date 数据类型,它包含年、月和日。

3

xsd:string 用于名字和姓氏。

我们将此文件保存为 hr.xsd

3.4. 服务契约

服务契约通常表示为一个WSDL文件。请注意,在 Spring-WS 中,无需手动编写 WSDL。基于 XSD 和一些约定,Spring-WS 可以为您创建 WSDL,如标题为第 3.6 节,“实现端点”的一节所述。如果您愿意,可以跳到下一节;本节的其余部分将向您展示如何手动编写自己的 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 的抽象部分(可以说是接口),剩下具体部分。具体部分包括一个 binding,它告诉客户端如何调用您刚刚定义的操作;以及一个 service,它告诉客户端在何处调用它。

添加具体部分是相当标准的:只需引用您之前定义的抽象部分,确保对 soap:binding 元素使用document/literalrpc/encoded 已弃用),为操作选择一个 soapAction(在本例中为 http://mycompany.com/RequestHoliday,但任何 URI 都可以),并确定您希望请求进入的 location URL(在本例中为 http://mycompany.com/humanresources

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:schema="http://mycompany.com/hr/schemas"
                  xmlns:tns="http://mycompany.com/hr/definitions"
                  targetNamespace="http://mycompany.com/hr/definitions">
    <wsdl:types>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <xsd:import namespace="http://mycompany.com/hr/schemas"                      (1)
                schemaLocation="hr.xsd"/>
        </xsd:schema>
    </wsdl:types>
    <wsdl:message name="HolidayRequest">                                                 (2)
        <wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>               (3)
    </wsdl:message>
    <wsdl:portType name="HumanResource">                                                 (4)
        <wsdl:operation name="Holiday">
            <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>             (2)
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="HumanResourceBinding" type="tns:HumanResource">                  (4)(5)
        <soap:binding style="document"                                                   (6)
            transport="http://schemas.xmlsoap.org/soap/http"/>                           (7)
        <wsdl:operation name="Holiday">
            <soap:operation soapAction="http://mycompany.com/RequestHoliday"/>           (8)
            <wsdl:input name="HolidayRequest">
                <soap:body use="literal"/>                                               (6)
            </wsdl:input>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="HumanResourceService">
        <wsdl:port binding="tns:HumanResourceBinding" name="HumanResourcePort">          (5)
            <soap:address location="http://localhost:8080/holidayService/"/>             (9)
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

1

我们导入第 3.3 节,“数据契约”中定义的模式。

2

我们定义 HolidayRequest 消息,该消息在 portType 中使用。

3

HolidayRequest 类型在模式中定义。

4

我们定义 HumanResource 端口类型,该端口类型在 binding 中使用。

5

我们定义 HumanResourceBinding 绑定,该绑定在 port 中使用。

6

我们使用 document/literal 风格。

7

字面值 http://schemas.xmlsoap.org/soap/http 表示 HTTP 传输。

8

soapAction 属性表示将随每个请求发送的 SOAPAction HTTP 头部。

9

http://localhost:8080/holidayService/ 地址是调用 Web 服务的 URL。

这是最终的 WSDL。我们将在下一节中描述如何实现由此产生的模式和 WSDL。

3.5. 创建项目

在本节中,我们将使用Maven3为我们创建初始项目结构。这样做不是必需的,但极大地减少了我们为设置 HolidayService 所需编写的代码量。

以下命令使用 Spring-WS 原型(即项目模板)为我们创建一个 Maven3 Web 应用程序项目

mvn archetype:create -DarchetypeGroupId=org.springframework.ws \
  -DarchetypeArtifactId=spring-ws-archetype \
  -DarchetypeVersion=2.1.4.RELEASE \
  -DgroupId=com.mycompany.hr \
  -DartifactId=holidayService

此命令将创建一个名为 holidayService 的新目录。在此目录中,有一个 'src/main/webapp' 目录,它将包含 WAR 文件的根目录。您将在此处找到标准的 Web 应用程序部署描述符 'WEB-INF/web.xml',它定义了一个 Spring-WS MessageDispatcherServlet 并将所有传入请求映射到此 Servlet。

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">

    <display-name>MyCompany HR Holiday Service</display-name>

    <!-- take especial notice of the name of this servlet -->
    <servlet>
        <servlet-name>spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-ws</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

除了上述 'WEB-INF/web.xml' 文件外,您还需要另一个 Spring-WS 特有的配置文件,名为 'WEB-INF/spring-ws-servlet.xml'。此文件包含所有 Spring-WS 特有的 Bean,例如 EndPointsWebServiceMessageReceivers 等等,并用于创建一个新的 Spring 容器。此文件的名称派生自伴随 Servlet 的名称(在本例中为 'spring-ws'),并附加 '-servlet.xml'。因此,如果您定义了一个名为 'dynamite'MessageDispatcherServlet,则 Spring-WS 特有的配置文件的名称将是 'WEB-INF/dynamite-servlet.xml'

(您可以在???中查看此示例的 'WEB-INF/spring-ws-servlet.xml' 文件内容。)

创建项目结构后,您可以将上一节的模式和 WSDL 放入 'WEB-INF/' 文件夹。

3.6. 实现端点

在 Spring-WS 中,您将实现端点(Endpoints)来处理传入的 XML 消息。通常通过使用 @Endpoint 注解来标注类来创建端点。在此端点类中,您将创建一个或多个处理传入请求的方法。方法签名可以非常灵活:您可以包含几乎任何与传入 XML 消息相关的参数类型,这将在后面解释。

3.6.1. 处理 XML 消息

在此示例应用程序中,我们将使用JDom来处理 XML 消息。我们还在使用XPath,因为它允许我们选择 XML JDOM 树的特定部分,而无需严格的模式一致性。

package com.mycompany.hr.ws;

import java.text.SimpleDateFormat;
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.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.xpath.XPath;

@Endpoint                                                                                (1)
public class HolidayEndpoint {

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

  private XPath startDateExpression;

  private XPath endDateExpression;

  private XPath nameExpression;

  private HumanResourceService humanResourceService;

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

    Namespace namespace = Namespace.getNamespace("hr", NAMESPACE_URI);

    startDateExpression = XPath.newInstance("//hr:StartDate");
    startDateExpression.addNamespace(namespace);

    endDateExpression = XPath.newInstance("//hr:EndDate");
    endDateExpression.addNamespace(namespace);

    nameExpression = XPath.newInstance("concat(//hr:FirstName,' ',//hr:LastName)");
    nameExpression.addNamespace(namespace);
  }

  @PayloadRoot(namespace = NAMESPACE_URI, localPart = "HolidayRequest")                  (3)
  public void handleHolidayRequest(@RequestPayload Element holidayRequest)               (4)
      throws Exception {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    Date startDate = dateFormat.parse(startDateExpression.valueOf(holidayRequest));
    Date endDate = dateFormat.parse(endDateExpression.valueOf(holidayRequest));
    String name = nameExpression.valueOf(holidayRequest);

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

}

1

HolidayEndpoint@Endpoint 注解进行了标注。这使得该类成为一种特殊的 @Component,适用于在 Spring-WS 中处理 XML 消息,并使其适合进行组件扫描。

2

HolidayEndpoint 需要 HumanResourceService 业务服务来操作,因此我们通过构造函数注入依赖项,并使用 @Autowired 注解进行标注。接下来,我们使用 JDOM API 设置 XPath 表达式。有三个表达式://hr:StartDate 用于提取 <StartDate> 的文本值,//hr:EndDate 用于提取结束日期,以及 concat(//hr:FirstName,' ',//hr:LastName) 用于提取和连接员工姓名。

3

@PayloadRoot 注解告诉 Spring-WS,handleHolidayRequest 方法适用于处理 XML 消息。该方法可以处理的消息类型由注解值指定,在本例中,它可以处理具有 HolidayRequest 本地部分和 http://mycompany.com/hr/schemas 命名空间的 XML 元素。有关将消息映射到端点的更多信息,请参见下一节。

4

handleHolidayRequest(..) 方法是主要的处理方法,它接收传入 XML 消息中的 <HolidayRequest/> 元素。 @RequestPayload 注解表示holidayRequest 参数应映射到请求消息的有效载荷。我们使用 XPath 表达式从 XML 消息中提取字符串值,并使用 SimpleDateFormat 将这些值转换为 Date 对象。利用这些值,我们调用业务服务上的方法。通常,这将导致启动数据库事务,并更改数据库中的一些记录。最后,我们定义一个 void 返回类型,这表示 Spring-WS 不想发送响应消息。如果我们想要一个响应消息,我们可以返回一个表示响应消息有效载荷的 JDOM Element。

使用 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>2.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>jdom</groupId>
        <artifactId>jdom</artifactId>
        <version>1.0</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1</version>
    </dependency>
</dependencies>

以下是我们如何在我们的 spring-ws-servlet.xml Spring XML 配置文件中使用组件扫描来配置这些类。我们还通过 <sws:annotation-driven> 元素指示 Spring-WS 使用注解驱动的端点。

<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.6.2. 将消息路由到端点

在编写端点的过程中,我们还使用了 @PayloadRoot 注解来指示 handleHolidayRequest 方法可以处理哪类消息。在 Spring-WS 中,此过程由 EndpointMapping 负责。在此,我们通过使用 PayloadRootAnnotationMethodEndpointMapping 根据消息内容进行路由。上面使用的注解

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

基本意味着,无论何时收到具有命名空间 http://mycompany.com/hr/schemas 和本地名称 HolidayRequest 的 XML 消息,它都将被路由到 handleHolidayRequest 方法。通过在我们的配置中使用 <sws:annotation-driven> 元素,我们启用了对 @PayloadRoot 注解的检测。在一个端点中拥有多个相关的处理方法是可能的(并且很常见),每个方法处理不同的 XML 消息。

还有其他将端点映射到 XML 消息的方法,这些方法将在下一章中描述。

3.6.3. 提供服务和存根实现

现在我们有了端点,我们需要 HumanResourceService 及其实现供 HolidayEndpoint 使用。

package com.mycompany.hr.service;

import java.util.Date;

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

出于教程目的,我们将使用 HumanResourceService 的一个简单存根实现。

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 注解进行了标注。这使得该类成为业务外观,使其成为在 HolidayEndpoint 中由 @Autowired 注入的候选项。

3.7. 发布 WSDL

最后,我们需要发布 WSDL。如第 3.4 节,“服务契约”中所述,我们不需要自己编写 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

ID 决定了可以检索 WSDL 的 URL。在本例中,ID 是 holiday,这意味着可以在 Servlet 上下文中以 holiday.wsdl 的形式检索 WSDL。完整的 URL 通常是 http://localhost:8080/holidayService/holiday.wsdl

3

接下来,我们将 WSDL 端口类型设置为 HumanResource

4

我们设置了服务可访问的位置:/holidayService/。我们使用相对 URI,并指示框架将其动态转换为绝对 URI。因此,如果服务部署到不同的上下文,我们无需手动更改 URI。更多信息,请参阅第 5.3.1.1 节,“自动 WSDL 暴露”

为了使位置转换生效,我们需要在 web.xml 中的 spring-ws servlet 中添加一个初始化参数

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

5

我们定义 WSDL 定义本身的目标命名空间。设置此属性不是必需的。如果未设置,WSDL 将具有与 XSD 模式相同的命名空间。

2

xsd 元素引用了我们在第 3.3 节,“数据契约”中定义的人力资源模式。我们只需将模式放在应用程序的 WEB-INF 目录中。

您可以使用mvn install创建 WAR 文件。如果您部署应用程序(到 Tomcat、Jetty 等),并将浏览器指向此位置,您将看到生成的 WSDL。此 WSDL 可供客户端使用,例如soapUI或其他 SOAP 框架。

本教程到此结束。教程代码可在 Spring-WS 的完整发行版中找到。下一步是查看发行版中的 echo 示例应用程序。之后,查看 airline 示例,它稍微复杂一些,因为它使用了 JAXB、WS-Security、Hibernate 和事务服务层。最后,您可以阅读其余的参考文档。