第 5 章 使用 Spring-WS 建立 Web 服務

5.1. 簡介

Spring-WS 的伺服器端支援圍繞一個 MessageDispatcher 設計,該排程器將傳入訊息分派給端點,並可配置端點對映、響應生成和端點攔截。端點通常使用 @Endpoint 註解,並且有一個或多個處理方法。這些方法透過檢查訊息的各個部分(通常是有效負載)來處理傳入的 XML 請求訊息,並建立某種響應。您可以使用另一個註解(通常是 @PayloadRoot)來註解該方法,以指示它可以處理哪種型別的訊息。

Spring-WS 的 XML 處理非常靈活。一個端點可以從 Spring-WS 支援的大量 XML 處理庫中進行選擇,包括 DOM 系列(W3C DOM、JDOM、dom4j 和 XOM)、用於更快效能的 SAX 或 StAX、用於從訊息中提取資訊的 XPath,甚至編組技術(JAXB、Castor、XMLBeans、JiBX 或 XStream)來將 XML 轉換為物件,反之亦然。

5.2. MessageDispatcher

Spring-WS 的伺服器端圍繞一箇中心類設計,該類將傳入的 XML 訊息分派給端點。Spring-WS 的 MessageDispatcher 非常靈活,允許您使用任何型別的類作為端點,只要它可以在 Spring IoC 容器中進行配置。從某種意義上說,訊息排程器類似於 Spring 的 DispatcherServlet,即 Spring Web MVC 中使用的“前端控制器”。

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 暴露給客戶端。

舉例來說,考慮以下在 Spring-WS 配置檔案(/WEB-INF/[servlet-name]-servlet.xml)中定義的 <static-wsdl> 定義。請注意“id”屬性的值,因為這將在暴露 WSDL 時使用。

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

然後,可以透過對以下形式的 URL(根據需要替換主機、埠和 servlet 上下文路徑)進行 GET 請求來訪問“Orders.wsdl”檔案中定義的 WSDL。

https://:8080/spring-ws/orders.wsdl

備註

所有 WsdlDefinition bean 定義都由 MessageDispatcherServlet 以其 bean id(或 bean 名稱)加上 .wsdl 字尾的形式公開。因此,如果 bean id 是 echo,主機名是“server”,Servlet 上下文(war 名稱)是“spring-ws”,則可以透過 http://server/spring-ws/echo.wsdl 獲取 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 模式生成 WSDL。這是第 3.7 節,“釋出 WSDL”中顯示的方法。以下應用程式上下文片段顯示瞭如何建立此類動態 WSDL 檔案

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

<dynamic-wsdl> 透過使用約定從 XSD 模式構建 WSDL。它遍歷模式中找到的所有 element 元素,併為所有元素建立一個 message。接下來,它為所有以定義的請求或響應字尾結尾的訊息建立 WSDL operation。預設的請求字尾是 Request;預設的響應字尾是 Response,儘管這些可以透過分別設定 <dynamic-wsdl /> 上的 requestSuffixresponseSuffix 屬性來更改。它還根據操作構建 portTypebindingservice

例如,如果我們的 Orders.xsd 模式定義了 GetOrdersRequestGetOrdersResponse 元素,則 <dynamic-wsdl> 將建立一個 GetOrdersRequestGetOrdersResponse 訊息,以及一個 GetOrders 操作,該操作位於 Orders 埠型別中。

如果您想透過包含或匯入使用多個模式,您需要將 Commons XMLSchema 放在類路徑上。如果 Commons XMLSchema 在類路徑上,則上述 <dynamic-wsdl> 元素將遵循所有 XSD 匯入和包含,並將它們作為單個 XSD 內聯到 WSDL 中。這大大簡化了模式的部署,同時仍然可以單獨編輯它們。

<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,但我們可以透過向 servlet 的 Web 應用程式上下文新增 WebServiceMessageReceiverHandlerAdapter 來指示它委託給 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。此訊息偵聽器需要 WebServiceMessageFactoryMessageDispatcher 才能執行。以下配置片段顯示了這一點

<beans>

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="vm://?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 IDLE 的監視策略。IDLE 命令是 IMAP 電子郵件協議的可選擴充套件,允許郵件伺服器非同步地向 MailMessageReceiver 傳送新訊息更新。如果您使用支援 IDLE 命令的 IMAP 伺服器,您可以將 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)。您只需要定義伺服器例項並將其配置為處理傳入請求。Core Spring Framework 中的 remoting 模組包含 HTTP 伺服器的便捷工廠 bean:SimpleHttpServerFactoryBean。最重要的屬性是 contexts,它將上下文路徑對映到相應的 HttpHandler

Spring Web Services 提供了 HttpHandler 介面的 2 種實現: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 的支援與其他傳輸非常相似:WebServiceTemplate 有一個 XmppMessageSenderMessageDispatcher 有一個 XmppMessageReceiver

以下示例展示瞭如何設定伺服器端 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 一樣,預設情況下被作用域為單例,即每個容器建立一個 bean 定義例項。作為單例意味著多個執行緒可以同時使用它,因此端點必須是執行緒安全的。如果您想使用不同的作用域,例如原型,請參閱 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, String, org.w3c.Node, org.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.SoapHeaderorg.springframework.ws.soap.SoapHeaderElement 在與 @SoapHeader 註解結合使用時。預設啟用。
JAXB2任何用 javax.xml.bind.annotation.XmlRootElementjavax.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

  • 字串

  • 節點

  • 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.XmlRootElementjavax.xml.bind.JAXBElement 註解的型別。當 JAXB2 在類路徑上時啟用。
OXMSpring OXM Marshaller 支援的任何型別。當指定了 <sws:annotation-driven/>marshaller 屬性時啟用。

如您所見,定義處理方法簽名有很多可能性。甚至可以擴充套件此機制,並支援您自己的引數型別。請參閱 DefaultMethodEndpointAdapterMethodReturnValueHandler 的類級別 Javadoc 以瞭解如何操作。

5.5. 端點對映

端點對映負責將傳入訊息對映到適當的端點。有一些開箱即用的端點對映,例如 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 節,“端點對映”中描述的 @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 時,響應將不會發送回客戶端。

您可以在 Web 服務中使用一些標準的 EndpointInterceptor 實現。此外,還有 XwsSecurityInterceptor,在第 7.2 節,“XwsSecurityInterceptor中進行了描述。

5.5.2.1. PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor

在開發 Web 服務時,記錄傳入和傳出 XML 訊息可能很有用。SWS 透過 PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor 類實現了這一點。前者將訊息的有效負載記錄到 Commons Logging Log 中;後者記錄整個 SOAP 包,包括 SOAP 頭。以下示例顯示瞭如何在端點對映中定義它們

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

</beans>

這兩個攔截器都有兩個屬性:“logRequest”和“logResponse”,可以設定為 false 以停用請求或響應訊息的日誌記錄。

5.5.2.2. PayloadValidatingInterceptor

使用契約優先開發風格的好處之一是我們可以使用模式來驗證傳入和傳出 XML 訊息。Spring-WS 透過 PayloadValidatingInterceptor 實現了這一點。此攔截器需要引用一個或多個 W3C XML 或 RELAX NG 模式,並且可以設定為驗證請求或響應,或兩者。

備註

請注意,請求驗證聽起來是個好主意,但會使最終的 Web 服務非常嚴格。通常,請求是否驗證並不真正重要,只在端點能否獲得足夠的資訊來完成請求時才重要。驗證響應個好主意,因為端點應該遵循其模式。記住波斯特爾定律:“對你所做的要保守,對你從別人那裡接受的要開放。

這是一個使用 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 提供了 EndpointExceptionResolvers,以緩解當訊息由匹配請求的端點處理時發生的意外異常的痛苦。端點異常解析器有點類似於在 Web 應用程式描述符 web.xml 中定義的異常對映。但是,它們提供了一種更靈活的方式來處理異常。它們提供有關丟擲異常時呼叫了哪個端點的資訊。此外,程式化處理異常的方式為您提供了更多選項來適當響應。您可以使用您想要的任何方式處理異常,例如透過返回具有特定故障程式碼和字串的 SOAP 故障,而不是透過給出異常和堆疊跟蹤來暴露應用程式內部。

端點異常解析器由 MessageDispatcher 自動拾取,因此無需顯式配置。

除了實現 EndpointExceptionResolver 介面(這只是實現 resolveException(MessageContext, endpoint, Exception) 方法的問題)之外,您還可以使用其中一個提供的實現。最簡單的實現是 SimpleSoapExceptionResolver,它只建立一個 SOAP 1.1 Server 或 SOAP 1.2 Receiver Fault,並使用異常訊息作為故障字串。SimpleSoapExceptionResolver 是預設的,但可以透過顯式新增另一個解析器來覆蓋它。

5.6.1. SoapFaultMappingExceptionResolver

SoapFaultMappingExceptionResolver 是一個更復雜的實現。此解析器使您能夠獲取可能丟擲的任何異常的類名並將其對映到 SOAP 故障,如下所示

<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 型別的異常對映到客戶端 SOAP 故障,其故障字串為 "Invalid request",如下面的響應所示

<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 故障。為了使這些註解被拾取,您需要在應用程式上下文中新增 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。這兩個類都是編組器支援的物件。例如,它們可以具有 @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,您的 IDE 將為您提供可能的請求建立策略列表,前提是您靜態匯入了 RequestCreators。同樣適用於 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 故障。
mustUnderstandFault()clientOrSenderFault()serverOrReceiverFault()versionMismatchFault()預期響應訊息包含特定的 SOAP 故障。

您可以透過鏈式呼叫 andExpect() 來設定多個響應期望,如下所示

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

有關 ResponseMatchers 提供的請求匹配器的更多資訊,請參閱類級別 Javadoc。



[2] 有關 WS-Addressing 的更多資訊,請參見 http://en.wikipedia.org/wiki/WS-Addressing

© . This site is unofficial and not affiliated with VMware.