第 5 章. 使用 Spring-WS 创建 Web 服务

5.1. 简介

Spring-WS 的服务器端支持围绕着一个 MessageDispatcher 设计,它将接收到的消息分派给端点(endpoint),并支持可配置的端点映射、响应生成和端点拦截。端点通常使用 @Endpoint 注解进行标注,并具有一个或多个处理方法。这些方法通过检查消息的各个部分(通常是负载,payload)来处理接收到的 XML 请求消息,并生成某种响应。您可以使用另一个注解(通常是 @PayloadRoot)来标注方法,以指示它可以处理哪种类型的消息。

Spring-WS 的 XML 处理非常灵活。端点可以选择 Spring-WS 支持的大量 XML 处理库中的任何一个,包括 DOM 系列(W3C DOM、JDOM、dom4j 和 XOM)、用于更快性能的 SAX 或 StAX、用于从消息中提取信息的 XPath,甚至是编组(marshalling)技术(JAXB、Castor、XMLBeans、JiBX 或 XStream)来实现 XML 到对象以及对象到 XML 的转换。

5.2. MessageDispatcher

Spring-WS 的服务器端围绕一个中心类设计,该类将接收到的 XML 消息分派给端点。Spring-WS 的 MessageDispatcher 非常灵活,只要类可以在 Spring IoC 容器中配置,就可以用作端点。从某种意义上说,消息分派器类似于 Spring 的 DispatcherServlet,后者是 Spring Web MVC 中使用的“前端控制器(Front Controller)”。

MessageDispatcher 的处理和分派流程如下图所示。

Spring Web Services 中的请求处理工作流程

MessageDispatcher 设置好并且接收到针对该特定分派器的请求时,MessageDispatcher 便开始处理请求。以下列表描述了请求由 MessageDispatcher 处理的完整过程。

  1. 使用配置好的 EndpointMapping(s) 搜索适当的端点。如果找到端点,将执行与该端点关联的调用链(前置处理器、后置处理器和端点),以创建响应。

  2. 为端点搜索适当的适配器。MessageDispatcher 将请求委托给此适配器来调用端点。

  3. 如果返回响应,则将其发送。如果未返回响应(例如,由于前置或后置处理器出于安全原因等拦截了请求),则不发送响应。

请求处理过程中抛出的异常会被应用上下文中声明的任何端点异常解析器捕获。使用这些异常解析器可以定义自定义行为(例如,在抛出此类异常时返回 SOAP 故障)。

MessageDispatcher 具有多个属性,用于设置端点适配器、映射异常解析器。但是,设置这些属性不是必需的,因为分派器会自动检测应用上下文中注册的所有这些类型。只有当需要覆盖自动检测时,才应设置这些属性。

消息分派器操作的是消息上下文,而不是传输特定的输入流和输出流。因此,传输特定的请求需要读取到 MessageContext 中。对于 HTTP,这可以通过 WebServiceMessageReceiverHandlerAdapter 完成,它是一个 Spring Web HandlerInterceptor,这样 MessageDispatcher 就可以被配置在一个标准的 DispatcherServlet 中。然而,有一种更方便的方法,如第 5.3.1 节,“MessageDispatcherServlet 所示。

5.3. 传输

Spring Web Services 支持多种传输协议。最常见的是 HTTP 传输,为此提供了一个自定义 servlet,但也可以通过 JMS 甚至电子邮件发送消息。

5.3.1. MessageDispatcherServlet

MessageDispatcherServlet 是一个标准的 Servlet,它方便地扩展自标准的 Spring Web DispatcherServlet,并包装了一个 MessageDispatcher。因此,它结合了它们的特性:作为 MessageDispatcher,它遵循上一节描述的相同请求处理流程。作为 servlet,MessageDispatcherServlet 在您的 Web 应用程序的 web.xml 中配置。您希望由 MessageDispatcherServlet 处理的请求必须在同一个 web.xml 文件中使用 URL 映射进行映射。这是标准的 Java EE servlet 配置;下面是一个 MessageDispatcherServlet 声明和映射的示例。

<web-app>

    <servlet>
        <servlet-name>spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

</web-app>

在上面的示例中,所有请求都将由 'spring-ws' MessageDispatcherServlet 处理。这只是设置 Spring Web Services 的第一步,因为 Spring-WS 框架使用的各种组件 bean 也需要配置;此配置由标准的 Spring XML <bean/> 定义组成。由于 MessageDispatcherServlet 是一个标准的 Spring DispatcherServlet,它将在您的 Web 应用程序的 WEB-INF 目录中查找名为 [servlet-name]-servlet.xml 的文件,并在 Spring 容器中创建其中定义的 bean。在上面的示例中,这意味着它将查找 '/WEB-INF/spring-ws-servlet.xml'。此文件将包含所有 Spring Web Services bean,例如端点、编组器等等。

5.3.1.1. 自动公开 WSDL

MessageDispatcherServlet 将自动检测在其 Spring 容器中定义的任何 WsdlDefinition bean。所有检测到的 WsdlDefinition bean 也将通过 WsdlDefinitionHandlerAdapter 公开;这是一种非常方便的方式,只需定义一些 bean 即可向客户端公开您的 WSDL。

例如,考虑以下 <static-wsdl> 定义,它定义在 Spring-WS 配置文件(/WEB-INF/[servlet-name]-servlet.xml)中。注意 'id' 属性的值,因为它将在公开 WSDL 时使用。

<sws:static-wsdl id="orders" location="/WEB-INF/wsdl/orders.wsdl"/>

定义在 'Orders.wsdl' 文件中的 WSDL 然后可以通过以下形式的 URL 的 GET 请求访问(请根据实际情况替换主机、端口和 servlet 上下文路径)。

http://localhost:8080/spring-ws/orders.wsdl

注意

所有 WsdlDefinition bean 定义都由 MessageDispatcherServlet 以其 bean ID(或 bean 名称)加上 .wsdl 后缀的形式公开。因此,如果 bean ID 是 echo,主机名是 "server",Servlet 上下文(war 名)是 "spring-ws",则 WSDL 可以通过 http://server/spring-ws/echo.wsdl 获取。

MessageDispatcherServlet(或者更准确地说,是 WsdlDefinitionHandlerAdapter)的另一个不错的功能是,它能够转换其公开的所有 WSDL 中 'location' 的值,以反映传入请求的 URL。

请注意,此 'location' 转换功能默认情况下是关闭的。要开启此功能,您只需为 MessageDispatcherServlet 指定一个初始化参数,如下所示。

<web-app>

  <servlet>
    <servlet-name>spring-ws</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    <init-param>
      <param-name>transformWsdlLocations</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>

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

</web-app>

请查阅 WsdlDefinitionHandlerAdapter 类的类级别 Javadoc,以了解有关整个转换过程的更多信息。

除了手工编写 WSDL 并使用 <static-wsdl> 公开它之外,Spring Web Services 还可以从 XSD Schema 生成 WSDL。这是第 3.7 节,“发布 WSDL” 中展示的方法。下一个应用上下文片段展示了如何创建这样的动态 WSDL 文件。

<sws:dynamic-wsdl id="orders"
    portTypeName="Orders"
    locationUri="http://localhost:8080/ordersService/">
  <sws:xsd location="/WEB-INF/xsd/Orders.xsd"/>
</sws:dynamic-wsdl>

<dynamic-wsdl> 使用约定从 XSD Schema 构建 WSDL。它遍历 Schema 中找到的所有 element 元素,并为所有元素创建一个 message。接下来,它为所有以定义的请求或响应后缀结尾的消息创建 WSDL operation。默认的请求后缀是 Request;默认的响应后缀是 Response,尽管这些可以通过在 <dynamic-wsdl /> 上设置 requestSuffixresponseSuffix 属性来更改。它还基于这些操作构建 portTypebindingservice

例如,如果我们的 Orders.xsd Schema 定义了 GetOrdersRequestGetOrdersResponse 元素,则 <dynamic-wsdl> 将创建一个 GetOrdersRequestGetOrdersResponse 消息,以及一个 GetOrders 操作,该操作会放入一个 Orders portType 中。

如果您想使用多个 Schema(通过 include 或 import),您需要将 Commons XMLSchema 放在类路径上。如果 Commons XMLSchema 在类路径上,上面的 <dynamic-wsdl> 元素将遵循所有 XSD import 和 include,并将它们以内联方式作为单个 XSD 放入 WSDL 中。这极大地简化了 Schema 的部署,同时仍然可以单独编辑它们。

<dynamic-wsdl> 元素依赖于 DefaultWsdl11Definition 类。该定义类使用 org.springframework.ws.wsdl.wsdl11.provider 包中的 WSDL 提供者和 ProviderBasedWsdl4jDefinition 来在第一次请求时生成 WSDL。请参阅这些类的类级别 Javadoc,了解如何在必要时扩展此机制。

注意

尽管在运行时从 XSD 创建 WSDL 非常方便,但这种方法也有一些缺点。首先,尽管我们努力保持不同版本之间的 WSDL 生成过程一致,但仍然存在可能发生(轻微)变化的可能性。其次,生成过程有点慢,尽管一旦生成,WSDL 会被缓存以供后续引用。

因此,建议仅在项目开发阶段使用 <dynamic-wsdl>。然后,我们建议使用浏览器下载生成的 WSDL,将其存储在项目中,并使用 <static-wsdl> 进行公开。这是确保 WSDL 不随时间变化的唯一方法。

5.3.2. 在 DispatcherServlet 中配置 Spring-WS

作为 MessageDispatcherServlet 的替代方案,您可以在标准的 Spring-Web MVC DispatcherServlet 中配置 MessageDispatcher。默认情况下,DispatcherServlet 只能委托给 Controllers,但我们可以通过添加 WebServiceMessageReceiverHandlerAdapter 到 servlet 的 Web 应用上下文来指示它委托给 MessageDispatcher

<beans>

    <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="defaultHandler" ref="messageDispatcher"/>
    </bean

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>

    ...

    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
</beans>

注意,通过显式添加 WebServiceMessageReceiverHandlerAdapter,分派器 servlet 不会加载默认的适配器,因此无法处理标准的 Spring-MVC Controllers。因此,我们在最后添加了 SimpleControllerHandlerAdapter

以类似的方式,您可以配置 WsdlDefinitionHandlerAdapter,以确保 DispatcherServlet 可以处理 WsdlDefinition 接口的实现。

<beans>

    <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"/>

    <bean class="org.springframework.ws.transport.http.WsdlDefinitionHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
           <props>
             <prop key="*.wsdl">myServiceDefinition</prop>
           </props>
        </property>
        <property name="defaultHandler" ref="messageDispatcher"/>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>

    <bean id="myServiceDefinition" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
       <prop name="wsdl" value="/WEB-INF/myServiceDefintion.wsdl"/>
    </bean>

    ...

</beans>

5.3.3. JMS 传输

Spring Web Services 通过 Spring 框架提供的 JMS 功能支持服务器端 JMS 处理。Spring Web Services 提供了 WebServiceMessageListener 以便插入到 MessageListenerContainer 中。此消息监听器需要一个 WebServiceMessageFactory 和一个 MessageDispatcher 才能工作。以下配置片段展示了这一点。

<beans>

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
    </bean>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destinationName" value="RequestQueue"/>
        <property name="messageListener">
            <bean class="org.springframework.ws.transport.jms.WebServiceMessageListener">
                <property name="messageFactory" ref="messageFactory"/>
                <property name="messageReceiver" ref="messageDispatcher"/>
            </bean>
        </property>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>
</beans>

作为 WebServiceMessageListener 的替代方案,Spring Web Services 提供了一个 WebServiceMessageDrivenBean,它是一个 EJB MessageDrivenBean。有关 EJB 的更多信息,请参阅 WebServiceMessageDrivenBean 的类级别 Javadoc。

5.3.4. 电子邮件传输

除了 HTTP 和 JMS 之外,Spring Web Services 还提供服务器端电子邮件处理。此功能通过 MailMessageReceiver 类提供。此类监控 POP3 或 IMAP 文件夹,将电子邮件转换为 WebServiceMessage,并使用 SMTP 发送任何响应。主机名可以通过 storeUri 进行配置,它指示要监控传入请求的邮件文件夹(通常是 POP3 或 IMAP 文件夹),以及一个 transportUri,它指示用于发送响应的服务器(通常是 SMTP 服务器)。

MailMessageReceiver 如何监控传入消息可以通过可插拔的策略进行配置:MonitoringStrategy。默认情况下,使用轮询策略,每五分钟轮询一次传入文件夹以查找新消息。此间隔可以通过设置策略的 pollingInterval 属性来更改。默认情况下,所有 MonitoringStrategy 实现都会删除已处理的消息;这可以通过设置 deleteMessages 属性来更改。

作为效率较低的轮询方法的替代方案,有一种监控策略使用 IMAP IDLEIDLE 命令是 IMAP 电子邮件协议的可选扩展,它允许邮件服务器异步地向 MailMessageReceiver 发送新消息更新。如果您使用的 IMAP 服务器支持 IDLE 命令,则可以将 ImapIdleMonitoringStrategy 插入到 monitoringStrategy 属性中。除了支持的服务器外,您还需要使用 JavaMail 1.4.1 或更高版本。

以下配置片段展示了如何使用服务器端电子邮件支持,并将默认轮询间隔覆盖为每 30 秒检查一次(30,000 毫秒)。

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="messagingReceiver" class="org.springframework.ws.transport.mail.MailMessageReceiver">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="from" value="Spring-WS SOAP Server &lt;[email protected]&gt;"/>
        <property name="storeUri" value="imap://server:[email protected]/INBOX"/>
        <property name="transportUri" value="smtp://smtp.example.com"/>
        <property name="messageReceiver" ref="messageDispatcher"/>
        <property name="monitoringStrategy">
            <bean class="org.springframework.ws.transport.mail.monitor.PollingMonitoringStrategy">
                <property name="pollingInterval" value="30000"/>
            </bean>
        </property>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>
</beans>

5.3.5. 嵌入式 HTTP 服务器传输

Spring Web Services 提供了一种基于 Sun 的 JRE 1.6 HTTP 服务器 的传输。嵌入式 HTTP 服务器是一个独立的服务器,配置简单。它提供了一种比传统 servlet 容器更轻量级的替代方案。

使用嵌入式 HTTP 服务器时,无需外部部署描述符(web.xml)。您只需要定义服务器实例并将其配置为处理传入请求。Spring 核心框架中的 remoting 模块包含一个用于 HTTP 服务器的方便的工厂 bean:SimpleHttpServerFactoryBean。最重要的属性是 contexts,它将上下文路径映射到相应的 HttpHandler

Spring Web Services 提供了两种 HttpHandler 接口的实现:WsdlDefinitionHttpHandlerWebServiceMessageReceiverHttpHandler。前者将传入的 GET 请求映射到 WsdlDefinition。后者负责处理 Web 服务消息的 POST 请求,因此需要一个 WebServiceMessageFactory(通常是 SaajSoapMessageFactory)和一个 WebServiceMessageReceiver(通常是 SoapMessageDispatcher)来完成其任务。

与 servlet 世界类比,contexts 属性扮演着 web.xml 中 servlet 映射的角色,而 WebServiceMessageReceiverHttpHandler 相当于 MessageDispatcherServlet

以下片段显示了一个简单的 HTTP 服务器传输配置示例。

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
    
    <bean id="messageReceiver" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings" ref="endpointMapping"/>
    </bean>

    <bean id="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
        <property name="defaultEndpoint" ref="stockEndpoint"/>
    </bean>
    
    <bean id="httpServer" class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
        <property name="contexts">
            <map>
                <entry key="/StockService.wsdl" value-ref="wsdlHandler"/>
                <entry key="/StockService" value-ref="soapHandler"/>
            </map>
        </property>
    </bean>

    <bean id="soapHandler" class="org.springframework.ws.transport.http.WebServiceMessageReceiverHttpHandler">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="messageReceiver" ref="messageReceiver"/>
    </bean>

    <bean id="wsdlHandler" class="org.springframework.ws.transport.http.WsdlDefinitionHttpHandler">
        <property name="definition" ref="wsdlDefinition"/>
    </bean>
</beans>

有关 SimpleHttpServerFactoryBean 的更多信息,请参阅其Javadoc

5.3.6. XMPP 传输

最后,Spring Web Services 2.0 引入了对 XMPP(也称为 Jabber)的支持。该支持基于 Smack 库。

Spring Web Services 对 XMPP 的支持与其他传输非常相似:有一个用于 WebServiceTemplateXmppMessageSender,以及一个用于 MessageDispatcherXmppMessageReceiver

以下示例展示了如何设置服务器端 XMPP 组件。

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
        <property name="host" value="jabber.org"/>
        <property name="username" value="username"/>
        <property name="password" value="password"/>
    </bean>

    <bean id="messagingReceiver" class="org.springframework.ws.transport.xmpp.XmppMessageReceiver">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="connection" ref="connection"/>
        <property name="messageReceiver" ref="messageDispatcher"/>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>

</beans>

5.4. 端点

端点是 Spring-WS 服务器端支持的核心概念。端点提供对应用程序行为的访问,该行为通常由业务服务接口定义。端点解释 XML 请求消息,并使用该输入(通常)调用业务服务上的方法。服务调用的结果表示为响应消息。Spring-WS 有各种各样的端点,使用不同的方式来处理 XML 消息并创建响应。

您可以通过使用 @Endpoint 注解标注类来创建一个端点。在类中,您可以定义一个或多个处理传入 XML 请求的方法,通过使用各种参数类型(例如 DOM 元素、JAXB2 对象等)。您可以使用另一个注解(通常是 @PayloadRoot)来指示方法可以处理哪种类型的消息。

考虑以下示例端点。

package samples;

import org.w3c.dom.Element;

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.soap.SoapHeader;

@Endpoint                                                                                (1)
public class AnnotationOrderEndpoint {

  private final OrderService orderService;

  @Autowired                                                                             (2)
  public AnnotationOrderEndpoint(OrderService orderService) {
      this.orderService = orderService;
  }

  @PayloadRoot(localPart = "order", namespace = "http://samples")                        (5)
  public void order(@RequestPayload Element orderElement) {                              (3)
    Order order = createOrder(orderElement);
    orderService.createOrder(order);
  }

  @PayloadRoot(localPart = "orderRequest", namespace = "http://samples")                 (5)
  @ResponsePayload
  public Order getOrder(@RequestPayload OrderRequest orderRequest, SoapHeader header) {  (4)
    checkSoapHeaderForSomething(header);
    return orderService.getOrder(orderRequest.getId());
  }

  ...

}

1

类使用 @Endpoint 进行标注,将其标记为 Spring-WS 端点。

2

构造函数使用 @Autowired 进行标注,以便将 OrderService 业务服务注入到此端点中。

3

order 方法接受一个 Element 作为参数,并使用 @RequestPayload 进行标注。这意味着消息的负载将作为 DOM 元素传递给此方法。该方法具有 void 返回类型,表明不发送响应消息。

有关端点方法的更多信息,请参阅第 5.4.1 节,“@Endpoint 处理方法”

4

getOrder 方法接受一个 OrderRequest 作为参数,也使用 @RequestPayload 进行标注。此参数是 JAXB2 支持的对象(它使用 @XmlRootElement 进行标注)。这意味着消息的负载将作为反编组后的对象传递给此方法。SoapHeader 类型也作为参数给出。调用时,此参数将包含请求消息的 SOAP 头部。该方法也使用 @ResponsePayload 进行标注,表明返回值(Order)将用作响应消息的负载。

有关端点方法的更多信息,请参阅第 5.4.1 节,“@Endpoint 处理方法”

5

此端点的两个处理方法都使用 @PayloadRoot 进行标注,指示可以处理哪种类型的请求消息:getOrder 方法将为本地名称为 orderRequest 且命名空间 URI 为 http://samples 的请求调用;order 方法将为本地名称为 order 的请求调用。

有关 @PayloadRoot 的更多信息,请参阅第 5.5 节,“端点映射”

要启用对 @Endpoint 和相关 Spring-WS 注解的支持,您需要在 Spring 应用上下文中添加以下内容。

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  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">

  <sws:annotation-driven />

</beans>

在接下来的几节中,将更详细地描述 @Endpoint 编程模型。

注意

端点,像其他 Spring Bean 一样,默认情况下作用域是单例(singleton),即每个容器创建一个 bean 定义的实例。单例意味着多个线程可以同时使用它,因此端点必须是线程安全的。如果您想使用不同的作用域,例如 prototype,请参阅Spring 参考文档

请注意,Spring-WS 中提供的所有抽象基类都是线程安全的,除非在类级别 Javadoc 中另有说明。

5.4.1. @Endpoint 处理方法

为了使端点能够实际处理传入的 XML 消息,它需要有一个或多个处理方法。处理方法可以接受各种参数和返回类型,但通常它们有一个参数用于包含消息负载,并返回响应消息的负载(如果有)。您将在本节中了解支持哪些参数和返回类型。

为了指示方法可以处理哪种类型的消息,该方法通常使用 @PayloadRoot@SoapAction 注解进行标注。您将在第 5.5 节,“端点映射” 中了解有关这些注解的更多信息。

以下是处理方法的一个示例。

@PayloadRoot(localPart = "order", namespace = "http://samples")
public void order(@RequestPayload Element orderElement) {
  Order order = createOrder(orderElement);
  orderService.createOrder(order);
}

order 方法接受一个 Element 作为参数,并使用 @RequestPayload 进行标注。这意味着消息的负载将作为 DOM 元素传递给此方法。该方法具有 void 返回类型,表明不发送响应消息。

5.4.1.1. 处理方法参数

处理方法通常有一个或多个参数,它们引用传入 XML 消息的各个部分。最常见的是,处理方法会有一个单独的参数映射到消息的负载,但也可以映射到请求消息的其他部分,例如 SOAP 头部。本节将描述您可以在处理方法签名中使用的参数。

要将参数映射到请求消息的负载,您需要使用 @RequestPayload 注解标注此参数。此注解告诉 Spring-WS 该参数需要绑定到请求负载。

下表描述了支持的参数类型。它显示了支持的类型、是否需要 @RequestPayload 注解以及其他注意事项。

名称支持的参数类型需要 @RequestPayload 吗?附加说明
TrAX javax.xml.transform.Source 及其子接口(DOMSourceSAXSourceStreamSourceStAXSource默认启用。
W3C DOMorg.w3c.dom.Element默认启用
dom4jorg.dom4j.Element当 dom4j 在类路径上时启用。
JDOMorg.jdom.Element当 JDOM 在类路径上时启用。
XOMnu.xom.Element当 XOM 在类路径上时启用。
StAX javax.xml.stream.XMLStreamReaderjavax.xml.stream.XMLEventReader当 StAX 在类路径上时启用。
XPath任何 boolean、double、Stringorg.w3c.Nodeorg.w3c.dom.NodeList,或者可以通过 Spring 3 转换服务String 转换且用 @XPathParam 注解的类型。默认启用,请参阅第 5.4.1.1.1 节,“@XPathParam
消息上下文org.springframework.ws.context.MessageContext默认启用。
SOAP org.springframework.ws.soap.SoapMessageorg.springframework.ws.soap.SoapBodyorg.springframework.ws.soap.SoapEnvelopeorg.springframework.ws.soap.SoapHeader 以及与 @SoapHeader 注解结合使用时的 org.springframework.ws.soap.SoapHeaderElement默认启用。
JAXB2任何使用 javax.xml.bind.annotation.XmlRootElement 注解的类型,以及 javax.xml.bind.JAXBElement当 JAXB2 在类路径上时启用。
OXMSpring OXM Unmarshaller 支持的任何类型。<sws:annotation-driven/>unmarshaller 属性被指定时启用。

以下是一些可能的方法签名示例。

  • public void handle(@RequestPayload Element element)

    此方法将以请求消息的负载作为 DOM org.w3c.dom.Element 调用。

  • public void handle(@RequestPayload DOMSource domSource, SoapHeader header)

    此方法将以请求消息的负载作为 javax.xml.transform.dom.DOMSource 调用。参数 header 将绑定到请求消息的 SOAP 头部。

  • public void handle(@RequestPayload MyJaxb2Object requestObject, @RequestPayload Element element, Message messageContext)

    此方法将以反编组到 MyJaxb2Object(使用 @XmlRootElement 注解标注)的请求消息负载调用。消息的负载也作为 DOM Element 提供。整个消息上下文作为第三个参数传递。

如您所见,定义处理方法签名有很多可能性。甚至可以扩展此机制,支持您自己的参数类型。请参阅 DefaultMethodEndpointAdapterMethodArgumentResolver 的类级别 Javadoc,了解如何操作。

5.4.1.1.1. @XPathParam

一种参数类型需要额外说明:@XPathParam。这里的想法是,您只需使用 XPath 表达式标注一个或多个方法参数,并且每个这样的标注参数将绑定到表达式的评估结果。下面是一个示例。

package samples;

import javax.xml.transform.Source;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.Namespace;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.XPathParam;

@Endpoint
public class AnnotationOrderEndpoint {

  private final OrderService orderService;

  public AnnotationOrderEndpoint(OrderService orderService) {
    this.orderService = orderService;
  }

  @PayloadRoot(localPart = "orderRequest", namespace = "http://samples")
  @Namespace(prefix = "s", uri="http://samples")
  public Order getOrder(@XPathParam("/s:orderRequest/@id") int orderId) {
    Order order = orderService.getOrder(orderId);
    // create Source from order and return it
}

}

由于我们在 XPath 表达式中使用了前缀 's',我们必须将其绑定到 http://samples 命名空间。这可以通过 @Namespace 注解来实现。或者,我们可以将此注解放在类型级别以用于所有处理程序方法,甚至可以放在包级别(在 package-info.java 中)以用于多个端点。

使用 @XPathParam,您可以绑定到 XPath 支持的所有数据类型。

  • booleanBoolean

  • doubleDouble

  • String

  • Node

  • NodeList

除了此列表外,您可以使用任何可以通过 Spring 3 转换服务String 转换的类型。

5.4.1.2. 处理方法返回类型

要发送响应消息,处理方法需要指定返回类型。如果不需要响应消息,方法可以简单地声明 void 返回类型。最常见的是,返回类型用于创建响应消息的负载,但也可以映射到响应消息的其他部分。本节将描述您可以在处理方法签名中使用的返回类型。

要将返回值映射到响应消息的负载,您需要使用 @ResponsePayload 注解标注该方法。此注解告诉 Spring-WS 返回值将绑定到响应负载。

下表描述了支持的返回类型。它显示了支持的类型、参数是否需要用 @ResponsePayload 进行注解,以及其他注释。

名称支持的返回类型需要 @ResponsePayload 注解吗?附加说明
无响应 void 默认启用。
TrAX javax.xml.transform.Source 及其子接口(DOMSourceSAXSourceStreamSourceStAXSource默认启用。
W3C DOMorg.w3c.dom.Element默认启用
dom4jorg.dom4j.Element当 dom4j 在类路径上时启用。
JDOMorg.jdom.Element当 JDOM 在类路径上时启用。
XOMnu.xom.Element当 XOM 在类路径上时启用。
JAXB2任何使用 javax.xml.bind.annotation.XmlRootElement 注解的类型,以及 javax.xml.bind.JAXBElement当 JAXB2 在类路径上时启用。
OXM任何 Spring OXM Marshaller 支持的类型。当指定了 <sws:annotation-driven/>marshaller 属性时启用。

正如你所见,在定义处理方法签名时有很多可能性。甚至可以扩展此机制,并支持你自己的参数类型。请参考 DefaultMethodEndpointAdapterMethodReturnValueHandler 的类级别 Javadoc 以了解具体方法。

5.5. Endpoint 映射

端点映射(Endpoint Mapping)负责将传入的消息映射到适当的端点(endpoint)。有一些端点映射是默认启用的,例如 PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping,但我们首先来看一下 EndpointMapping 的一般概念。

一个 EndpointMapping 提供一个 EndpointInvocationChain,其中包含与传入请求匹配的端点,并且可能还包含一个将应用于请求和响应的端点拦截器列表。当请求进来时,MessageDispatcher 会将其交给端点映射,让它检查请求并生成一个适当的 EndpointInvocationChain。然后 MessageDispatcher 将调用链中的端点和任何拦截器。

可配置的端点映射概念非常强大,它还可以选择包含拦截器(可以处理请求或响应,或两者)。许多辅助功能可以构建到自定义的 EndpointMapping 中。例如,可以有一个自定义的端点映射,它不仅根据消息内容,还根据特定的 SOAP 头(或多个 SOAP 头)来选择端点。

大多数端点映射继承自 AbstractEndpointMapping,它提供了一个 'interceptors' 属性,这是要使用的拦截器列表。EndpointInterceptor第 5.5.2 节 “拦截请求 - EndpointInterceptor 接口” 中讨论。此外,还有一个 'defaultEndpoint',当此端点映射没有找到匹配的端点时,将使用此默认端点。

第 5.4 节 “端点” 中所述,@Endpoint 风格允许你在一个端点类中处理多个请求。这是 MethodEndpointMapping 的职责。这个映射决定了对于传入的请求消息应该调用哪个方法。

有两种端点映射可以将请求导向方法:PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping,这两者都可以通过在你的应用程序上下文中使用 <sws:annotation-driven/> 来启用。

PayloadRootAnnotationMethodEndpointMapping 使用 @PayloadRoot 注解,带有 localPartnamespace 元素,用特定的限定名称标记方法。每当有载荷根元素具有此限定名称的消息传入时,该方法就会被调用。例如,请参见 上面

或者,SoapActionAnnotationMethodEndpointMapping 使用 @SoapAction 注解来用特定的 SOAP Action 标记方法。每当有消息传入并且带有此 SOAPAction 头时,该方法就会被调用。

5.5.1. WS-Addressing

WS-Addressing 指定了一种与传输无关的路由机制。它基于 ToAction SOAP 头,分别指示 SOAP 消息的目的地和意图。此外,WS-Addressing 允许你定义一个返回地址(用于普通消息和故障消息),以及一个可用于关联的唯一消息标识符[2]。以下是 WS-Addressing 消息的示例

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
    xmlns:wsa="http://www.w3.org/2005/08/addressing">
  <SOAP-ENV::Header>
    <wsa:MessageID>urn:uuid:21363e0d-2645-4eb7-8afd-2f5ee1bb25cf</wsa:MessageID>
    <wsa:ReplyTo>
      <wsa:Address>http://example.com/business/client1</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To S:mustUnderstand="true">http://example/com/fabrikam</wsa:To>
    <wsa:Action>http://example.com/fabrikam/mail/Delete</wsa:Action>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <f:Delete xmlns:f="http://example.com/fabrikam">
      <f:maxCount>42</f:maxCount>
    </f:Delete>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

在此示例中,目的地设置为 http://example/com/fabrikam,而动作设置为 http://example.com/fabrikam/mail/Delete。此外,还有一个消息标识符和回复地址。默认情况下,此地址是“匿名”地址,表示响应应使用与请求相同的通道发送(即 HTTP 响应),但它也可以是另一个地址,如本示例所示。

在 Spring Web Services 中,WS-Addressing 作为端点映射实现。使用此映射,你可以将 WS-Addressing 动作与端点相关联,类似于上面描述的 SoapActionAnnotationMethodEndpointMapping

5.5.1.1. AnnotationActionEndpointMapping

AnnotationActionEndpointMapping 类似于 SoapActionAnnotationMethodEndpointMapping,但使用 WS-Addressing 头而不是 SOAP Action 传输头。

要使用 AnnotationActionEndpointMapping,请使用 @Action 注解标记处理方法,类似于 第 5.4.1 节 “@Endpoint 处理方法”第 5.5 节 “Endpoint 映射” 中描述的 @PayloadRoot@SoapAction 注解。以下是示例

package samples;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.soap.addressing.server.annotation.Action

@Endpoint
public class AnnotationOrderEndpoint {
    private final OrderService orderService;

    public AnnotationOrderEndpoint(OrderService orderService) {
        this.orderService = orderService;
    }

    @Action("http://samples/RequestOrder")
    public Order getOrder(OrderRequest orderRequest) {
        return orderService.getOrder(orderRequest.getId());
    }

    @Action("http://samples/CreateOrder")
    public void order(Order order) {
        orderService.createOrder(order);
    }

}

上面的映射将具有 WS-Addressing Actionhttp://samples/RequestOrder 的请求路由到 getOrder 方法。具有 http://samples/CreateOrder 的请求将路由到 order 方法。

默认情况下,AnnotationActionEndpointMapping 支持 WS-Addressing 的 1.0 版(2006 年 5 月)和 2004 年 8 月版。这两个版本最受欢迎,并可与 Axis 1 和 2、JAX-WS、XFire、Windows Communication Foundation (WCF) 和 Windows Services Enhancements (WSE) 3.0 互操作。如有必要,可以将特定版本的规范注入到 versions 属性中。

除了 @Action 注解之外,你还可以用 @Address 注解标记类。如果设置了,其值将与传入消息的 To 头属性进行比较。

最后,还有一个 messageSenders 属性,这是将响应消息发送到非匿名、带外地址所必需的。你可以在此属性中设置 MessageSender 实现,就像在 WebServiceTemplate 上设置一样。参见 第 6.2.1.1 节 “URI 和传输”

5.5.2. 拦截请求 - EndpointInterceptor 接口

端点映射机制具有端点拦截器的概念。当你想要对某些请求应用特定功能时,例如处理与安全相关的 SOAP 头或日志记录请求和响应消息,这些功能非常有用。

端点拦截器通常通过在应用程序上下文中使用 <sws;interceptors > 元素来定义。在此元素中,你可以简单地定义适用于该应用程序上下文中定义的所有端点的端点拦截器 Bean。或者,你可以使用 <sws:payloadRoot><sws:soapAction> 元素来指定拦截器应适用于哪个载荷根名称或 SOAP 动作。例如

<sws:interceptors>
  <bean class="samples.MyGlobalInterceptor"/>
  <sws:payloadRoot namespaceUri="http://www.example.com">
    <bean class="samples.MyPayloadRootInterceptor"/>
  </sws:payloadRoot>
  <sws:soapAction value="http://www.example.com/SoapAction">
    <bean class="samples.MySoapActionInterceptor1"/>
    <ref bean="mySoapActionInterceptor2"/>
  </sws:soapAction>
</sws:interceptors>

<bean id="mySoapActionInterceptor2" class="samples.MySoapActionInterceptor2"/>

在这里,我们定义了一个“全局”拦截器(MyGlobalInterceptor),它拦截所有请求和响应。我们还定义了一个仅适用于载荷根命名空间为 http://www.example.com 的 XML 消息的拦截器。在这里,我们可以除了 namespaceUri 之外,还定义一个 localPart 属性,以进一步限制拦截器适用的消息。最后,我们定义了两个拦截器,当消息具有 http://www.example.com/SoapAction SOAP 动作时应用。注意第二个拦截器实际上是对 <interceptors> 元素之外的 Bean 定义的引用。你可以在 <interceptors> 元素内的任何位置使用 Bean 引用。

拦截器必须实现 org.springframework.ws.server 包中的 EndpointInterceptor 接口。此接口定义了三个方法,一个可用于在实际端点执行之前处理请求消息,一个可用于处理正常响应消息,另一个可用于处理故障消息,这两个都将在端点执行之后调用。这三个方法应提供足够的灵活性来进行各种预处理和后处理。

拦截器上的 handleRequest(..) 方法返回一个布尔值。您可以使用此方法来中断或继续调用链的处理。当此方法返回 true 时,端点执行链将继续;当它返回 false 时,MessageDispatcher 将此解释为拦截器本身已经处理完毕,并且不再继续执行调用链中的其他拦截器和实际端点。handleResponse(..)handleFault(..) 方法也具有布尔返回值。当这些方法返回 false 时,响应将不会发送回客户端。

有一些标准的 EndpointInterceptor 实现可以在你的 Web 服务中使用。此外,还有一个 XwsSecurityInterceptor,它在 第 7.2 节 “XwsSecurityInterceptor 中描述。

5.5.2.1. PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor

开发 Web 服务时,日志记录传入和传出的 XML 消息非常有用。SWS 通过 PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor 类提供了便利。前者仅将消息的载荷记录到 Commons Logging 日志中;后者记录整个 SOAP 信封,包括 SOAP 头。以下示例展示了如何在端点映射中定义它们

  <sws:interceptors>
    <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
  </sws:interceptors>

</beans>

这两个拦截器都有两个属性:'logRequest' 和 'logResponse',可以设置为 false 以禁用请求或响应消息的日志记录。

5.5.2.2. PayloadValidatingInterceptor

使用契约优先开发风格的一个好处是,我们可以使用模式(schema)来验证传入和传出的 XML 消息。Spring-WS 通过 PayloadValidatingInterceptor 提供了便利。此拦截器需要引用一个或多个 W3C XML 或 RELAX NG 模式,并且可以设置为验证请求或响应,或两者。

注意

请注意,请求验证听起来是个好主意,但这会使生成的 Web 服务非常严格。通常,请求是否验证并不重要,重要的是端点是否能够获得足够的信息来完成请求。验证响应个好主意,因为端点应遵守其模式。记住 Postel 定律:“对你所做的事要保守;对你从别人那里接受的事要自由。

以下是一个使用 PayloadValidatingInterceptor 的示例;在此示例中,我们使用 /WEB-INF/orders.xsd 中的模式来验证响应,但不验证请求。请注意,PayloadValidatingInterceptor 还可以通过 schemas 属性接受多个模式。

<bean id="validatingInterceptor"
        class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
    <property name="schema" value="/WEB-INF/orders.xsd"/>
    <property name="validateRequest" value="false"/>
    <property name="validateResponse" value="true"/>
</bean>

5.5.2.3. PayloadTransformingInterceptor

为了将载荷转换为另一种 XML 格式,Spring Web Services 提供了 PayloadTransformingInterceptor。此端点拦截器基于 XSLT 样式表,在支持多个版本的 Web 服务时特别有用:你可以将旧的消息格式转换为新格式。以下是使用 PayloadTransformingInterceptor 的示例

<bean id="transformingInterceptor"
        class="org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor">
    <property name="requestXslt" value="/WEB-INF/oldRequests.xslt"/>
    <property name="responseXslt" value="/WEB-INF/oldResponses.xslt"/>
</bean>

我们只是使用 /WEB-INF/oldRequests.xslt 转换请求,并使用 /WEB-INF/oldResponses.xslt 转换响应消息。请注意,由于端点拦截器是在端点映射级别注册的,你可以简单地创建一个适用于“旧式”消息的端点映射,并将拦截器添加到该映射中。因此,转换将仅应用于这些“旧式”消息。

5.6. 处理异常

Spring-WS 提供了 EndpointExceptionResolver,以减轻在端点处理与请求匹配的消息时发生意外异常的痛苦。端点异常解析器有点类似于在 Web 应用程序描述符 web.xml 中定义的异常映射。但是,它们提供了更灵活的异常处理方式。它们提供了关于异常抛出时调用了哪个端点的信息。此外,编程方式处理异常为你提供了更多适当响应的选项。你不再需要通过暴露应用程序内部细节来给出异常和堆栈跟踪,而是可以以你想要的方式处理异常,例如通过返回带有特定故障代码和字符串的 SOAP 故障。

端点异常解析器会自动被 MessageDispatcher 拾取,因此不需要显式配置。

除了实现 EndpointExceptionResolver 接口(这只是实现 resolveException(MessageContext, endpoint, Exception) 方法的问题)之外,你还可以使用提供的实现之一。最简单的实现是 SimpleSoapExceptionResolver,它只是创建一个 SOAP 1.1 Server 或 SOAP 1.2 Receiver Fault,并使用异常消息作为故障字符串。SimpleSoapExceptionExceptionResolver 是默认设置,但可以通过显式添加另一个解析器来覆盖。

5.6.1. SoapFaultMappingExceptionResolver

SoapFaultMappingExceptionResolver 是一个更复杂的实现。此解析器使你能够获取任何可能抛出的异常的类名,并将其映射到 SOAP Fault,如下所示

<beans>
    <bean id="exceptionResolver"
        class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
        <property name="defaultFault" value="SERVER"/>
        <property name="exceptionMappings">
            <value>
                org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request
            </value>
        </property>
    </bean>
</beans>

键值和默认端点使用 faultCode,faultString,locale 格式,其中只有故障代码是必需的。如果未设置故障字符串,则默认为异常消息。如果未设置语言,则默认为英语。上述配置将类型为 ValidationFailureException 的异常映射到具有故障字符串 "Invalid request" 的客户端 SOAP Fault,如下面的响应所示

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
       <SOAP-ENV:Fault>
           <faultcode>SOAP-ENV:Client</faultcode>
           <faultstring>Invalid request</faultstring>
       </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

如果发生任何其他异常,它将返回默认故障:一个服务器端故障,异常消息作为故障字符串。

5.6.2. SoapFaultAnnotationExceptionResolver

最后,也可以用 @SoapFault 注解标记异常类,以指示每当抛出该异常时应返回的 SOAP Fault。为了使这些注解被拾取,你需要将 SoapFaultAnnotationExceptionResolver 添加到你的应用程序上下文中。注解的元素包括故障代码枚举、故障字符串或原因以及语言。以下是异常示例

package samples;

import org.springframework.ws.soap.server.endpoint.annotation.FaultCode;
import org.springframework.ws.soap.server.endpoint.annotation.SoapFault;

@SoapFault(faultCode = FaultCode.SERVER)
public class MyBusinessException extends Exception {

    public MyClientException(String message) {
        super(message);
    }
}

每当在端点调用期间抛出带有构造函数字符串 "Oops!"MyBusinessException 时,它将导致以下响应

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
       <SOAP-ENV:Fault>
           <faultcode>SOAP-ENV:Server</faultcode>
           <faultstring>Oops!</faultstring>
       </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

5.7. 服务器端测试

在测试 Web 服务端点时,有两种可能的方法

  • 编写单元测试,其中为端点提供(模拟)参数以供消费。

    这种方法的优点是很容易实现(尤其是对于用 @Endpoint 注解的类);缺点是你并非真正测试通过网络发送的 XML 消息的确切内容。

  • 编写集成测试,它确实测试消息的内容。

第一种方法可以通过 EasyMock、JMock 等模拟框架轻松实现。下一节将重点介绍使用 Spring Web Services 2.0 中引入的测试功能编写集成测试。

5.7.1. 编写服务器端集成测试

Spring Web Services 2.0 引入了创建端点集成测试的支持。在此上下文中,端点是处理 (SOAP) 消息的类(参见 第 5.4 节 “端点”)。

集成测试支持位于 org.springframework.ws.test.server 包中。该包中的核心类是 MockWebServiceClient。基本思想是此客户端创建一个请求消息,然后将其发送给标准 MessageDispatcherServlet 应用程序上下文中配置的端点(参见 第 5.3.1 节 “MessageDispatcherServlet)。这些端点将处理消息并创建响应。然后客户端接收此响应,并根据已注册的期望进行验证。

MockWebServiceClient 的典型用法是:

  1. 通过调用 MockWebServiceClient.createClient(ApplicationContext)MockWebServiceClient.createClient(WebServiceMessageReceiver, WebServiceMessageFactory) 创建 MockWebServiceClient 实例。

  2. 通过调用 sendRequest(RequestCreator) 发送请求消息,可能使用 RequestCreators 中提供的默认 RequestCreator 实现(可以静态导入)。

  3. 通过调用 andExpect(ResponseMatcher) 设置响应期望,可能使用 ResponseMatchers 中提供的默认 ResponseMatcher 实现(可以静态导入)。可以通过链式调用 andExpect(ResponseMatcher) 设置多个期望。

注意

请注意,MockWebServiceClient(及相关类)提供了一个“流畅的”API,因此你通常可以使用 IDE 中的代码补全功能(即 ctrl-space)来指导你完成设置模拟服务器的过程。

注意

另请注意,你在单元测试中依赖 Spring Web Services 中可用的标准日志功能。有时检查请求或响应消息以找出特定测试失败的原因会很有用。更多信息请参见 第 4.4 节 “消息日志记录和跟踪”

例如,考虑这个简单的 Web 服务端点类

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

@Endpoint                                                                                (1)
public class CustomerEndpoint {

  @ResponsePayload                                                                       (2)
  public CustomerCountResponse getCustomerCount(                                         (2)
      @RequestPayload CustomerCountRequest request) {                                    (2)
    CustomerCountResponse response = new CustomerCountResponse();
    response.setCustomerCount(10);
    return response;
  }

}

1

CustomerEndpoint@Endpoint 注解。参见 第 5.4 节 “端点”

2

getCustomerCount() 方法接收 CustomerCountRequest 作为参数,并返回 CustomerCountResponse。这两个类都是 Marshaller 支持的对象。例如,它们可以有一个 @XmlRootElement 注解以供 JAXB2 支持。

CustomerEndpoint 的典型测试如下所示

import javax.xml.transform.Source;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.xml.transform.StringSource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.ws.test.server.MockWebServiceClient;                          (1)
import static org.springframework.ws.test.server.RequestCreators.*;                      (1)
import static org.springframework.ws.test.server.ResponseMatchers.*;                     (1)

@RunWith(SpringJUnit4ClassRunner.class)                                                  (2)
@ContextConfiguration("spring-ws-servlet.xml")                                           (2)
public class CustomerEndpointIntegrationTest {

  @Autowired
  private ApplicationContext applicationContext;                                         (3)

  private MockWebServiceClient mockClient;

  @Before
  public void createClient() {
    mockClient = MockWebServiceClient.createClient(applicationContext);                  (4)
  }

  @Test
  public void customerEndpoint() throws Exception {
    Source requestPayload = new StringSource(
      "<customerCountRequest xmlns='http://springframework.org/spring-ws'>" +
        "<customerName>John Doe</customerName>" +
      "</customerCountRequest>");
    Source responsePayload = new StringSource(
      "<customerCountResponse xmlns='http://springframework.org/spring-ws'>" +
        "<customerCount>10</customerCount>" +
      "</customerCountResponse>");

    mockClient.sendRequest(withPayload(requestPayload)).                                 (5)
      andExpect(payload(responsePayload));                                               (5)
  }
}

1

CustomerEndpointIntegrationTest 导入了 MockWebServiceClient,并静态导入了 RequestCreatorsResponseMatchers

2

此测试使用了 Spring Framework 中提供的标准测试工具。这不是必需的,但通常是设置测试的最简单方法。

3

应用程序上下文是一个标准的 Spring-WS 应用程序上下文(参见 第 5.3.1 节 “MessageDispatcherServlet),从 spring-ws-servlet.xml 中读取。在这种情况下,应用程序上下文将包含 CustomerEndpoint 的 Bean 定义(或者可能使用了 <context:component-scan />)。

4

@Before 方法中,我们使用工厂方法 createClient 创建了一个 MockWebServiceClient

5

我们通过调用 sendRequest() 并传入由静态导入的 RequestCreators 提供的 withPayload() RequestCreator 来发送请求(参见 第 5.7.2 节 “RequestCreatorRequestCreators)。

我们还通过调用 andExpect() 并传入由静态导入的 ResponseMatchers 提供的 payload() ResponseMatcher 来设置响应期望(参见 第 5.7.3 节 “ResponseMatcherResponseMatchers)。

测试的这部分可能看起来有点令人困惑,但你的 IDE 的代码补全功能会非常有帮助。在输入 sendRequest( 后,只需键入 ctrl-space,如果已静态导入 RequestCreators,你的 IDE 将为你提供可能的请求创建策略列表。这同样适用于 andExpect(,前提是你已静态导入 ResponseMatchers

5.7.2. RequestCreatorRequestCreators

最初,MockWebServiceClient 需要为端点创建一个请求消息以供消费。客户端为此目的使用了 RequestCreator 策略接口

public interface RequestCreator {

  WebServiceMessage createRequest(WebServiceMessageFactory messageFactory)
    throws IOException;

}

你可以编写此接口的自己的实现,使用消息工厂创建请求消息,但这绝对不是必须的。RequestCreators 类提供了一种在 withPayload() 方法中基于给定载荷创建 RequestCreator 的方法。通常你会静态导入 RequestCreators

5.7.3. ResponseMatcherResponseMatchers

当请求消息被端点处理并接收到响应后,MockWebServiceClient 可以验证此响应消息是否符合某些期望。客户端为此目的使用了 ResponseMatcher 策略接口

public interface ResponseMatcher {

    void match(WebServiceMessage request,
               WebServiceMessage response)
      throws IOException, AssertionError;

}

你可以再次编写此接口的自己的实现,在消息不符合期望时抛出 AssertionError,但这绝对不是必须的,因为 ResponseMatchers 类为你提供了标准的 ResponseMatcher 实现,供你在测试中使用。通常你会静态导入此类。

ResponseMatchers 类提供了以下响应匹配器

ResponseMatchers 方法描述
payload()期望给定的响应载荷。
validPayload()期望响应载荷针对给定的 XSD 模式进行验证。
xpath()期望给定的 XPath 表达式存在、不存在或评估为给定值。
soapHeader()期望响应消息中存在给定的 SOAP 头。
noFault()期望响应消息不包含 SOAP Fault。
mustUnderstandFault(), clientOrSenderFault(), serverOrReceiverFault(), 和 versionMismatchFault()期望响应消息包含特定的 SOAP Fault。

你可以通过链式调用 andExpect() 来设置多个响应期望,如下所示

mockClient.sendRequest(...).
 andExpect(payload(expectedResponsePayload)).
 andExpect(validPayload(schemaResource));

有关 ResponseMatchers 提供的请求匹配器的更多信息,请参阅类级别的 Javadoc。



[2] 有关 WS-Addressing 的更多信息,请参见 http://en.wikipedia.org/wiki/WS-Addressing