第 6 章. 在客戶端使用 Spring Web Services

6.1. 簡介

Spring-WS 提供了一個客戶端 Web 服務 API,允許以一致的、XML 驅動的方式訪問 Web 服務。它還支援使用編組器(marshaller)和解組器(unmarshaller),以便您的服務層程式碼可以完全處理 Java 物件。

org.springframework.ws.client.core 包提供了使用客戶端訪問 API 的核心功能。它包含簡化 Web 服務使用的模板類,就像 Spring 核心的 JdbcTemplate 對 JDBC 所做的那樣。Spring 模板類通用的設計原則是提供輔助方法來執行常見操作,對於更復雜的用法,則委託給使用者實現的 callback 介面。Web 服務模板遵循相同的設計。這些類提供了各種便利方法,用於傳送和接收 XML 訊息,在傳送前將物件編組為 XML,並允許多種傳輸選項。

6.2. 使用客戶端 API

6.2.1. WebServiceTemplate

WebServiceTemplate 是 Spring-WS 中用於客戶端 Web 服務訪問的核心類。它包含傳送 Source 物件的方法,以及將響應訊息作為 SourceResult 接收的方法。此外,它還可以在透過傳輸傳送物件之前將其編組為 XML,並將任何響應 XML 解組回物件。

6.2.1.1. URI 和傳輸

WebServiceTemplate 類使用 URI 作為訊息目的地。您可以在模板本身上設定 defaultUri 屬性,或者在呼叫模板上的方法時顯式提供 URI。URI 將解析為 WebServiceMessageSender,後者負責透過傳輸層傳送 XML 訊息。您可以使用 WebServiceTemplate 類的 messageSendermessageSenders 屬性設定一個或多個訊息傳送器。

6.2.1.1.1. HTTP 傳輸

有兩種 WebServiceMessageSender 介面的實現用於透過 HTTP 傳送訊息。預設實現是 HttpUrlConnectionMessageSender,它使用 Java 自身提供的功能。另一種選擇是 HttpComponentsMessageSender,它使用 Apache HttpComponents HttpClient。如果您需要更高階且易於使用的功能(例如身份驗證、HTTP 連線池等),請使用後者。

要使用 HTTP 傳輸,請將 defaultUri 設定為類似 http://example.com/services 的內容,或者為其中一個方法提供 uri 引數。

以下示例展示瞭如何將預設配置用於 HTTP 傳輸

<beans>

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

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="defaultUri" value="http://example.com/WebService"/>
    </bean>

</beans>

以下示例展示瞭如何覆蓋預設配置,並使用 Apache HttpClient 進行 HTTP 身份驗證

<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
    <constructor-arg ref="messageFactory"/>
    <property name="messageSender">
        <bean class="org.springframework.ws.transport.http.HttpComponentsMessageSender">
            <property name="credentials">
                <bean class="org.apache.http.auth.UsernamePasswordCredentials">
                    <constructor-arg value="john:secret"/>
                </bean>
            </property>
        </bean>
    </property>
    <property name="defaultUri" value="http://example.com/WebService"/>
</bean>

6.2.1.1.2. JMS 傳輸

為了透過 JMS 傳送訊息,Spring Web Services 提供了 JmsMessageSender。此類使用 Spring 框架的功能將 WebServiceMessage 轉換為 JMS Message,在 QueueTopic 上傳送它,並接收響應(如果有)。

要使用 JmsMessageSender,您需要將 defaultUriuri 引數設定為 JMS URI,該 URI 至少包含 jms: 字首和目標名稱。一些 JMS URI 示例包括:jms:SomeQueuejms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENTjms:RequestQueue?replyToName=ResponseName。有關此 URI 語法的更多資訊,請參閱 JmsMessageSender 的類級別 Javadoc。

預設情況下,JmsMessageSender 傳送 JMS BytesMessage,但可以透過在 JMS URI 上使用 messageType 引數來覆蓋此設定以使用 TextMessages。例如:jms:Queue?messageType=TEXT_MESSAGE。請注意,BytesMessages 是首選型別,因為 TextMessages 不可靠地支援附件和字元編碼。

以下示例展示瞭如何將 JMS 傳輸與 ActiceMQ 連線工廠結合使用

<beans>

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

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

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.jms.JmsMessageSender">
                <property name="connectionFactory" ref="connectionFactory"/>
            </bean>
        </property>
        <property name="defaultUri" value="jms:RequestQueue?deliveryMode=NON_PERSISTENT"/>
    </bean>

</beans>

6.2.1.1.3. 電子郵件傳輸

Spring Web Services 還提供了一種電子郵件傳輸方式,可用於透過 SMTP 傳送 Web 服務訊息,並透過 POP3 或 IMAP 檢索它們。客戶端電子郵件功能包含在 MailMessageSender 類中。此類從請求 WebServiceMessage 建立電子郵件訊息,並透過 SMTP 傳送。然後,它等待響應訊息到達傳入的 POP3 或 IMAP 伺服器。

要使用 MailMessageSender,請將 defaultUriuri 引數設定為 mailto URI。以下是一些 URI 示例:mailto:[email protected]mailto:server@localhost?subject=SOAP%20Test。確保訊息傳送器已正確配置 transportUri(指示用於傳送請求的伺服器,通常是 SMTP 伺服器)和 storeUri(指示用於輪詢響應的伺服器,通常是 POP3 或 IMAP 伺服器)。

以下示例展示瞭如何使用電子郵件傳輸

<beans>

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

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.mail.MailMessageSender">
                <property name="from" value="Spring-WS SOAP Client &lt;[email protected]&gt;"/>
                <property name="transportUri" value="smtp://client:[email protected]"/>
                <property name="storeUri" value="imap://client:[email protected]/INBOX"/>
            </bean>
        </property>
        <property name="defaultUri" value="mailto:[email protected]?subject=SOAP%20Test"/>
    </bean>

</beans>

6.2.1.1.4. XMPP 傳輸

Spring Web Services 2.0 引入了 XMPP (Jabber) 傳輸,可用於透過 XMPP 傳送和接收 Web 服務訊息。客戶端 XMPP 功能包含在 XmppMessageSender 類中。此類從請求 WebServiceMessage 建立 XMPP 訊息,並透過 XMPP 傳送。然後它會監聽響應訊息的到來。

要使用 XmppMessageSender,請將 defaultUriuri 引數設定為 xmpp URI,例如 xmpp:[email protected]。傳送方還需要一個 XMPPConnection 才能工作,該連線可以使用 org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean 方便地建立。

以下示例展示瞭如何使用 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="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.xmpp.XmppMessageSender">
                <property name="connection" ref="connection"/>
            </bean>
        </property>
        <property name="defaultUri" value="xmpp:[email protected]"/>
    </bean>

</beans>

6.2.1.2. 訊息工廠

除了訊息傳送器,WebServiceTemplate 還需要一個 Web 服務訊息工廠。SOAP 有兩個訊息工廠:SaajSoapMessageFactoryAxiomSoapMessageFactory。如果未指定訊息工廠(透過 messageFactory 屬性),Spring-WS 將預設使用 SaajSoapMessageFactory

6.2.2. 傳送和接收 WebServiceMessage

WebServiceTemplate 包含許多方便的方法來發送和接收 Web 服務訊息。有些方法接受並返回 Source,有些方法返回 Result。此外,還有將物件編組和解組為 XML 的方法。這是一個將簡單 XML 訊息傳送到 Web 服務的示例。

import java.io.StringReader;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.transport.WebServiceMessageSender;

public class WebServiceClient {

    private static final String MESSAGE =
        "<message xmlns=\"http://tempuri.org\">Hello Web Service World</message>";

    private final WebServiceTemplate webServiceTemplate = new WebServiceTemplate();

    public void setDefaultUri(String defaultUri) {
        webServiceTemplate.setDefaultUri(defaultUri);
    }

    // send to the configured default URI
    public void simpleSendAndReceive() {
        StreamSource source = new StreamSource(new StringReader(MESSAGE));
        StreamResult result = new StreamResult(System.out);
        webServiceTemplate.sendSourceAndReceiveToResult(source, result);
    }

    // send to an explicit URI
    public void customSendAndReceive() {
        StreamSource source = new StreamSource(new StringReader(MESSAGE));
        StreamResult result = new StreamResult(System.out);
        webServiceTemplate.sendSourceAndReceiveToResult("https://:8080/AnotherWebService",
            source, result);
    }

}
<beans xmlns="http://www.springframework.org/schema/beans">

    <bean id="webServiceClient" class="WebServiceClient">
        <property name="defaultUri" value="https://:8080/WebService"/>
    </bean>

</beans>

上述示例使用 WebServiceTemplate 將 hello world 訊息傳送到位於 https://:8080/WebService 的 Web 服務(對於 simpleSendAndReceive() 方法),並將結果寫入控制檯。WebServiceTemplate 注入了預設 URI,因為 Java 程式碼中沒有顯式提供 URI,所以使用了預設 URI。

請注意,WebServiceTemplate 類一旦配置(假設其所有依賴項也是執行緒安全的,Spring-WS 附帶的所有依賴項都是如此),就是執行緒安全的,因此如果需要,多個物件可以使用相同的共享 WebServiceTemplate 例項。WebServiceTemplate 公開了一個零引數建構函式和 messageFactory/messageSender bean 屬性,可用於構造例項(使用 Spring 容器或純 Java 程式碼)。或者,考慮從 Spring-WS 的 WebServiceGatewaySupport 方便基類派生,該基類公開了方便的 bean 屬性以實現輕鬆配置。(您不必擴充套件此基類...它僅作為便利類提供。)

6.2.3. 傳送和接收 POJO - 編組和解組

為了方便傳送普通 Java 物件,WebServiceTemplate 有許多 send(..) 方法,它們以 Object 作為訊息資料內容的引數。WebServiceTemplate 類中的 marshalSendAndReceive(..) 方法將請求物件轉換為 XML 的工作委託給 Marshaller,並將響應 XML 轉換為物件的工作委託給 Unmarshaller。(有關編組器和解組器的更多資訊,請參閱 Spring 文件。)透過使用編組器,您的應用程式程式碼可以專注於正在傳送或接收的業務物件,而不必擔心它如何表示為 XML 的細節。為了使用編組功能,您必須使用 WebServiceTemplate 類的 marshaller/unmarshaller 屬性設定編組器和解組器。

6.2.4.  WebServiceMessageCallback

為了適應 SOAP 頭部和其他訊息設定,WebServiceMessageCallback 介面允許您在訊息建立之後但在傳送之前訪問訊息。以下示例演示如何設定透過編組物件建立的訊息上的 SOAP Action 頭部。

public void marshalWithSoapActionHeader(MyObject o) {

    webServiceTemplate.marshalSendAndReceive(o, new WebServiceMessageCallback() {

        public void doWithMessage(WebServiceMessage message) {
            ((SoapMessage)message).setSoapAction("http://tempuri.org/Action");
        }
    });
}

備註

請注意,您還可以使用 org.springframework.ws.soap.client.core.SoapActionCallback 來設定 SOAP Action 頭部。

6.2.4.1. WS-Addressing

除了伺服器端 WS-Addressing 支援之外,Spring Web Services 在客戶端也支援此規範。

要在客戶端設定 WS-Addressing 頭部,您可以使用 org.springframework.ws.soap.addressing.client.ActionCallback。此回撥將所需的 Action 頭部作為引數。它還有用於指定 WS-Addressing 版本和 To 頭部 的建構函式。如果未指定,To 頭部將預設為正在建立的連線的 URL。

以下是設定 Action 頭部為 http://samples/RequestOrder 的示例

webServiceTemplate.marshalSendAndReceive(o, new ActionCallback("http://samples/RequestOrder"));

6.2.5.  WebServiceMessageExtractor

WebServiceMessageExtractor 介面是一個低階回撥介面,允許您完全控制從接收到的 WebServiceMessage 中提取 Object 的過程。WebServiceTemplate 將在提供給它的 WebServiceMessageExtractor 上呼叫 extractData(..) 方法,同時與服務資源的底層連線仍然開啟。以下示例說明了 WebServiceMessageExtractor 的實際應用

public void marshalWithSoapActionHeader(final Source s) {
    final Transformer transformer = transformerFactory.newTransformer();
    webServiceTemplate.sendAndReceive(new WebServiceMessageCallback() {
        public void doWithMessage(WebServiceMessage message) {
            transformer.transform(s, message.getPayloadResult());
        },
        new WebServiceMessageExtractor() {
            public Object extractData(WebServiceMessage message) throws IOException
                // do your own transforms with message.getPayloadResult()
                //     or message.getPayloadSource()
            }
        });
}

6.3. 客戶端測試

在測試您的 Web 服務客戶端(即使用 WebServiceTemplate 訪問 Web 服務的類)時,有兩種可能的方法

  • 編寫單元測試,它只是模擬 WebServiceTemplate 類、WebServiceOperations 介面或完整的客戶端類。

    這種方法的優點是很容易實現;缺點是您並沒有真正測試透過網路傳送的 XML 訊息的確切內容,尤其是在模擬整個客戶端類時。

  • 編寫整合測試,它確實測試訊息的內容。

第一種方法可以很容易地透過 EasyMock、JMock 等模擬框架實現。下一節將重點介紹如何使用 Spring Web Services 2.0 中引入的測試功能編寫整合測試。

6.3.1. 編寫客戶端整合測試

Spring Web Services 2.0 引入了對建立 Web 服務客戶端整合測試的支援。在此上下文中,客戶端是使用 WebServiceTemplate 訪問 Web 服務的類。

整合測試支援位於 org.springframework.ws.test.client 包中。該包中的核心類是 MockWebServiceServer。其基本思想是 Web 服務模板連線到這個模擬伺服器,向其傳送請求訊息,然後模擬伺服器根據註冊的期望進行驗證。如果滿足期望,模擬伺服器然後準備一個響應訊息,並將其傳送回模板。

MockWebServiceServer 的典型用法是

  1. 透過呼叫 MockWebServiceServer.createServer(WebServiceTemplate)MockWebServiceServer.createServer(WebServiceGatewaySupport)MockWebServiceServer.createServer(ApplicationContext) 建立 MockWebServiceServer 例項。

  2. 透過呼叫 expect(RequestMatcher) 來設定請求期望,可能使用 RequestMatchers 中提供的預設 RequestMatcher 實現(可以靜態匯入)。可以透過鏈式呼叫 andExpect(RequestMatcher) 來設定多個期望。

  3. 透過呼叫 andRespond(ResponseCreator) 來建立適當的響應訊息,可能使用 ResponseCreators 中提供的預設 ResponseCreator 實現(可以靜態匯入)。

  4. 正常使用 WebServiceTemplate,無論是直接使用還是透過客戶端程式碼使用。

  5. 呼叫 MockWebServiceServer.verify() 以確保所有期望都已滿足。

備註

請注意,MockWebServiceServer(及相關類)提供了一個“流暢”的 API,因此您通常可以使用 IDE 中的程式碼完成功能(即 ctrl-space)來指導您完成設定模擬伺服器的過程。

備註

另請注意,您在單元測試中依賴於 Spring Web Services 中提供的標準日誌記錄功能。有時,檢查請求或響應訊息以找出特定測試失敗的原因可能很有用。有關更多資訊,請參閱第 4.4 節,“訊息日誌記錄和跟蹤”

例如,考慮這個 Web 服務客戶端類

import org.springframework.ws.client.core.support.WebServiceGatewaySupport;

public class CustomerClient extends WebServiceGatewaySupport {                           (1)

  public int getCustomerCount() {
    CustomerCountRequest request = new CustomerCountRequest();                           (2)
    request.setCustomerName("John Doe");

    CustomerCountResponse response =
      (CustomerCountResponse) getWebServiceTemplate().marshalSendAndReceive(request);    (3)
      
    return response.getCustomerCount();
  }

}

1

CustomerClient 擴充套件了 WebServiceGatewaySupport,後者為其提供了 webServiceTemplate 屬性。

2

CustomerCountRequest 是一個由編組器支援的物件。例如,它可以有一個 @XmlRootElement 註解以被 JAXB2 支援。

3

CustomerClient 使用 WebServiceGatewaySupport 提供的 WebServiceTemplate 將請求物件編組為 SOAP 訊息,並將其傳送到 Web 服務。響應物件被解組為 CustomerCountResponse

CustomerClient 的典型測試如下所示

import javax.xml.transform.Source;
import org.springframework.beans.factory.annotation.Autowired;
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 static org.junit.Assert.assertEquals;

import org.springframework.ws.test.client.MockWebServiceServer;                          (1)
import static org.springframework.ws.test.client.RequestMatchers.*;                      (1)
import static org.springframework.ws.test.client.ResponseCreators.*;                     (1)

@RunWith(SpringJUnit4ClassRunner.class)                                                  (2)
@ContextConfiguration("integration-test.xml")                                            (2)
public class CustomerClientIntegrationTest {

  @Autowired
  private CustomerClient client;                                                         (3)

  private MockWebServiceServer mockServer;                                               (4)

  @Before
  public void createServer() throws Exception {
    mockServer = MockWebServiceServer.createServer(client);
  }

  @Test
  public void customerClient() 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>");

    mockServer.expect(payload(requestPayload)).andRespond(withPayload(responsePayload)); (5)

    int result = client.getCustomerCount();                                              (6)
    assertEquals(10, result);                                                            (6)

    mockServer.verify();                                                                 (7)
  }

}

1

CustomerClientIntegrationTest 匯入了 MockWebServiceServer,並靜態匯入了 RequestMatchersResponseCreators

2

此測試使用 Spring 框架中提供的標準測試工具。這不是必需的,但通常是設定測試的最簡單方法。

3

CustomerClientintegration-test.xml 中配置,並使用 @Autowired 注入到此測試中。

4

@Before 方法中,我們使用 createServer 工廠方法建立一個 MockWebServiceServer

5

我們透過呼叫 expect() 並使用由靜態匯入的 RequestMatchers 提供的 payload() RequestMatcher 來定義期望(請參閱第 6.3.2 節,“RequestMatcherRequestMatchers)。

我們還透過呼叫 andRespond() 並使用由靜態匯入的 ResponseCreators 提供的 withPayload() ResponseCreator 來設定響應(請參閱第 6.3.3 節,“ResponseCreatorResponseCreators)。

測試的這部分可能看起來有點令人困惑,但 IDE 的程式碼完成功能非常有用。在輸入 expect( 後,只需輸入 ctrl-space,您的 IDE 就會為您提供可能的請求匹配策略列表,前提是您靜態匯入了 RequestMatchers。這同樣適用於 andRespond(,前提是您靜態匯入了 ResponseCreators

6

我們呼叫 CustomerClient 上的 getCustomerCount(),從而使用 WebServiceTemplate。模板此時已設定為“測試模式”,因此此方法呼叫不會建立真正的 (HTTP) 連線。我們還根據方法呼叫的結果進行了一些 JUnit 斷言。

7

我們呼叫 MockWebServiceServer 上的 verify(),從而驗證實際收到了預期的訊息。

6.3.2. RequestMatcherRequestMatchers

為了驗證請求訊息是否符合某些期望,MockWebServiceServer 使用 RequestMatcher 策略介面。此介面定義的契約非常簡單

public interface RequestMatcher {

  void match(URI uri,
             WebServiceMessage request)
    throws IOException,
           AssertionError;

}

您可以編寫自己的此介面實現,在訊息不符合您的期望時丟擲 AssertionError,但您當然不必這樣做。RequestMatchers 類為您提供了標準 RequestMatcher 實現,供您在測試中使用。您通常會靜態匯入此類。

RequestMatchers 類提供以下請求匹配器

RequestMatchers 方法描述
anything()期望任何型別的請求。
payload()期望給定的請求負載。
validPayload()期望請求負載根據給定的 XSD 模式進行驗證。
xpath()期望給定的 XPath 表示式存在,不存在,或評估為給定值。
soapHeader()期望請求訊息中存在給定的 SOAP 頭部。
connectionTo()期望連線到給定的 URL。

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

mockServer.expect(connectionTo("http://example.com")).
 andExpect(payload(expectedRequestPayload)).
 andExpect(validPayload(schemaResource)).
 andRespond(...);

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

6.3.3. ResponseCreatorResponseCreators

當請求訊息經過驗證並滿足定義的期望時,MockWebServiceServer 將為 WebServiceTemplate 建立一個響應訊息以供使用。伺服器為此目的使用 ResponseCreator 策略介面

public interface ResponseCreator {

  WebServiceMessage createResponse(URI uri,
                                   WebServiceMessage request,
                                   WebServiceMessageFactory messageFactory)
    throws IOException;

}

您同樣可以編寫自己的此介面實現,使用訊息工廠建立響應訊息,但您當然不必這樣做,因為 ResponseCreators 類為您提供了標準 ResponseCreator 實現,供您在測試中使用。您通常會靜態匯入此類。

ResponseCreators 類提供以下響應

ResponseCreators 方法描述
withPayload()建立具有給定負載的響應訊息。
withError()在響應連線中建立錯誤。此方法讓您有機會測試錯誤處理。
withException()從響應連線讀取時丟擲異常。此方法讓您有機會測試異常處理。
withMustUnderstandFault()withClientOrSenderFault()withServerOrReceiverFault()withVersionMismatchFault()建立具有給定 SOAP 錯誤的響應訊息。此方法讓您有機會測試故障處理。

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

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