© 2005-2020 原始作者。

您可以為自己使用和分發此文件的副本,前提是您不對這些副本收取任何費用,並且每份副本(無論是印刷版還是電子版)都包含此版權宣告。

前言

在當前的面向服務架構時代,越來越多的人使用 Web 服務來連線以前未連線的系統。最初,Web 服務被認為是進行遠端過程呼叫 (RPC) 的另一種方式。然而,隨著時間的推移,人們發現 RPC 和 Web 服務之間存在很大差異。特別是當與其他平臺的互操作性很重要時,傳送包含處理請求所需所有資料的封裝 XML 文件通常更好。從概念上講,基於 XML 的 Web 服務更適合與訊息佇列進行比較,而不是遠端解決方案。總的來說,XML 應該被視為資料的平臺中立表示,即 SOA 的通用語言。在開發或使用 Web 服務時,重點應該放在 XML 上,而不是 Java 上。

Spring Web Services 專注於建立這些文件驅動的 Web 服務。Spring Web Services 促進契約優先的 SOAP 服務開發,透過使用多種 XML 有效負載操作方法之一來建立靈活的 Web 服務。Spring-WS 提供了一個強大的訊息分派框架,一個與現有應用程式安全解決方案整合的 WS-Security 解決方案,以及一個遵循熟悉的 Spring 模板模式的客戶端 API

I. 引言

參考文件的第一部分概述了 Spring Web Services 及其底層概念。然後介紹了 Spring-WS,並解釋了契約優先 Web 服務開發背後的概念

1. 什麼是 Spring Web Services?

1.1. 簡介

Spring Web Services (Spring-WS) 是 Spring 社群的產品,專注於建立文件驅動的 Web 服務。Spring Web Services 旨在促進契約優先的 SOAP 服務開發,透過使用多種 XML 有效負載操作方法之一來建立靈活的 Web 服務。該產品基於 Spring 本身,這意味著您可以將 Spring 概念(例如依賴注入)作為 Web 服務不可或缺的一部分。

人們使用 Spring-WS 的原因有很多,但大多數人在發現其他 SOAP 棧在遵循 Web 服務最佳實踐方面存在不足後,都會被它吸引。Spring-WS 使最佳實踐變得容易。這包括 WS-I 基本配置檔案、契約優先開發以及契約與實現之間鬆散耦合等實踐。Spring Web Services 的其他主要功能是

1.1.1. 強大的對映

您可以根據訊息負載、SOAP Action 頭部或 XPath 表示式將傳入的 XML 請求分發到任何物件。

1.1.2. XML API 支援

傳入的 XML 訊息不僅可以使用標準的 JAXP API(如 DOM、SAX 和 StAX)進行處理,還可以使用 JDOM、dom4j、XOM 甚至編組技術進行處理。

1.1.3. 靈活的 XML 編組

Spring Web Services 建立在 Spring 框架中的物件/XML 對映模組之上,該模組支援 JAXB 1 和 2、Castor、XMLBeans、JiBX 和 XStream。

1.1.4. 重用您的 Spring 專長

Spring-WS 使用 Spring 應用程式上下文進行所有配置,這應該有助於 Spring 開發人員快速上手。此外,Spring-WS 的架構類似於 Spring-MVC。

1.1.5. 支援 WS-Security

WS-Security 允許您對 SOAP 訊息進行簽名、加密和解密,或對其進行身份驗證。

1.1.6. 與 Spring Security 整合

Spring Web Services 的 WS-Security 實現提供了與 Spring Security 的整合。這意味著您也可以將現有的 Spring Security 配置用於您的 SOAP 服務。

1.1.7. Apache 許可證

您可以放心地在專案中使用 Spring-WS。

1.2. 執行時環境

Spring Web Services 需要標準的 Java 8 執行時環境。Spring-WS 基於 Spring Framework 4.0.9 構建,但支援更高版本。

Spring-WS 由多個模組組成,本節的其餘部分將對此進行描述。

  • XML 模組 (spring-xml.jar) 包含 Spring Web Services 的各種 XML 支援類。此模組主要用於 Spring-WS 框架本身,而不是 Web 服務開發人員。

  • 核心模組 (spring-ws-core.jar) 是 Spring Web 服務功能的核心部分。它提供了核心的WebServiceMessageSoapMessage介面、伺服器端框架(具有強大的訊息分派)、用於實現 Web 服務端點的各種支援類以及客戶端 WebServiceTemplate

  • 支援模組 (spring-ws-support.jar) 包含額外的傳輸(JMS、電子郵件等)。

  • 安全包 (spring-ws-security.jar) 提供了一個與核心 Web 服務包整合的 WS-Security 實現。它允許您對 SOAP 訊息進行簽名、解密和加密,以及向其新增主體令牌。此外,它還允許您使用現有的 Spring Security 安全實現進行身份驗證和授權。

下圖顯示了 Spring-WS 模組之間的依賴關係。箭頭表示依賴關係(即,Spring-WS Core 依賴於 Spring-XML 和 Spring 3 及更高版本中的 OXM 模組)。

spring deps

1.3. 支援的標準

Spring Web Services 支援以下標準

  • SOAP 1.1 和 1.2

  • WSDL 1.1 和 2.0 (僅支援 WSDL 1.1 的基於 XSD 的生成)

  • WS-I Basic Profile 1.0, 1.1, 1.2, 和 2.0

  • WS-Addressing 1.0 和 2004 年 8 月草案

  • SOAP 訊息安全 1.1、使用者名稱令牌配置檔案 1.1、X.509 證書令牌配置檔案 1.1、SAML 令牌配置檔案 1.1、Kerberos 令牌配置檔案 1.1、基本安全配置檔案 1.1

2. 為何採用契約優先?

在建立 Web 服務時,有兩種開發風格:契約在後和契約優先。當您使用契約在後方法時,您從 Java 程式碼開始,並讓 Web 服務契約(在 WSDL 中——參見側邊欄)從其中生成。當使用契約優先時,您從 WSDL 契約開始,並使用 Java 實現契約。

什麼是 WSDL?

WSDL 代表 Web 服務描述語言。WSDL 檔案是描述 Web 服務的 XML 文件。它指定了服務的位置和該服務公開的操作(或方法)。有關 WSDL 的更多資訊,請參閱 WSDL 規範

Spring-WS 僅支援契約優先的開發風格,本節解釋了原因。

2.1. 物件/XML 阻抗不匹配

與 ORM 領域類似,我們有物件/關係阻抗不匹配,將 Java 物件轉換為 XML 也存在類似問題。乍一看,O/X 對映問題似乎很簡單:為每個 Java 物件建立一個 XML 元素,將所有 Java 屬性和欄位轉換為子元素或屬性。然而,事情並非如它們看起來那麼簡單,因為分層語言(如 XML,尤其是 XSD)和 Java 的圖模型之間存在根本區別。

本節的大部分內容都受到[alpine][effective-enterprise-java]的啟發。

2.1.1. XSD 擴充套件

在 Java 中,改變類行為的唯一方法是子類化它,將新行為新增到該子類中。在 XSD 中,您可以透過限制資料型別來擴充套件它——即,限制元素和屬性的有效值。例如,考慮以下示例

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

此型別透過正則表示式限制 XSD 字串,只允許三個大寫字母。如果此型別轉換為 Java,我們最終得到一個普通的 java.lang.String。正則表示式在轉換過程中丟失了,因為 Java 不允許這類擴充套件。

2.1.2. 不可移植的型別

Web 服務最重要的目標之一是互操作性:支援 Java、.NET、Python 等多個平臺。由於所有這些語言都有不同的類庫,因此您必須使用一些通用的、跨語言的格式在它們之間進行通訊。這種格式就是 XML,所有這些語言都支援它。

由於這種轉換,您必須確保在服務實現中使用可移植型別。例如,考慮一個返回 java.util.TreeMap 的服務

public Map getFlights() {
  // use a tree map, to make sure it's sorted
  TreeMap map = new TreeMap();
  map.put("KL1117", "Stockholm");
  ...
  return map;
}

毫無疑問,此對映的內容可以轉換為某種 XML,但由於沒有標準方法在 XML 中描述對映,因此它是專有的。此外,即使它可以轉換為 XML,許多平臺也沒有類似於 TreeMap 的資料結構。因此,當 .NET 客戶端訪問您的 Web 服務時,它可能最終得到一個具有不同語義的 System.Collections.Hashtable

在客戶端工作時也存在此問題。考慮以下 XSD 片段,它描述了一個服務契約

<element name="GetFlightsRequest">
  <complexType>
    <all>
      <element name="departureDate" type="date"/>
      <element name="from" type="string"/>
      <element name="to" type="string"/>
    </all>
  </complexType>
</element>

此契約定義了一個帶有 date 的請求,它是一個表示年、月、日的 XSD 資料型別。如果我們從 Java 呼叫此服務,我們可能會使用 java.util.Datejava.util.Calendar。然而,這兩個類實際上都描述的是時間,而不是日期。因此,我們實際上最終傳送的資料表示的是 2007 年 4 月 4 日午夜 (2007-04-04T00:00:00),這與 2007-04-04 不同。

2.1.3. 迴圈圖

假設我們有以下類結構

public class Flight {
  private String number;
  private List<Passenger> passengers;

  // getters and setters omitted
}

public class Passenger {
  private String name;
  private Flight flight;

  // getters and setters omitted
}

這是一個迴圈圖:Flight 引用 Passenger,而 Passenger 又引用 Flight。這樣的迴圈圖在 Java 中很常見。如果我們採用一種天真的方法將其轉換為 XML,我們最終會得到類似以下內容

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

處理這種結構可能需要很長時間才能完成,因為此迴圈沒有停止條件。

解決此問題的一種方法是使用對已編組物件的引用

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

這解決了遞迴問題,但引入了新問題。首先,您無法使用 XML 驗證器來驗證此結構。另一個問題是,在 SOAP 中使用這些引用的標準方式 (RPC/encoded) 已被棄用,取而代之的是 document/literal(請參閱 WS-I 基本配置檔案)。

這些只是處理 O/X 對映時的一些問題。在編寫 Web 服務時尊重這些問題很重要。尊重它們的最佳方式是完全專注於 XML,同時使用 Java 作為實現語言。這就是契約優先的全部意義所在。

2.2. 契約優先與契約在後

除了上一節中提到的物件/XML 對映問題外,還有其他原因偏愛契約優先的開發風格。

2.2.1. 脆弱性

如前所述,契約後置開發風格會導致您的 Web 服務契約(WSDL 和 XSD)從您的 Java 契約(通常是介面)生成。如果您使用這種方法,則無法保證契約隨時間保持不變。每次您更改 Java 契約並重新部署它時,Web 服務契約可能會隨之發生更改。

此外,並非所有 SOAP 棧都從 Java 契約生成相同的 Web 服務契約。這意味著更改您當前的 SOAP 棧以使用不同的棧(無論出於何種原因)也可能會更改您的 Web 服務契約。

當 Web 服務契約發生更改時,契約的使用者必須被告知獲取新契約並可能更改其程式碼以適應契約中的任何更改。

為了使契約有用,它必須儘可能長時間保持不變。如果契約發生更改,您必須聯絡服務的所有使用者,並指示他們獲取新版本的契約。

2.2.2. 效能

當 Java 物件自動轉換為 XML 時,無法確定透過網路傳送了什麼。一個物件可能引用另一個物件,後者又引用另一個物件,依此類推。最終,虛擬機器堆中的一半物件可能會轉換為 XML,從而導致響應時間變慢。

當使用契約優先時,您明確描述了傳送哪些 XML 到何處,從而確保這正是您想要的。

2.2.3. 可重用性

在單獨的檔案中定義您的模式允許您在不同的場景中重用該檔案。考慮在名為 airline.xsd 的檔案中定義 AirportCode

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

您可以使用 import 語句在其他模式甚至 WSDL 檔案中重用此定義。

2.2.4. 版本控制

儘管契約必須儘可能長時間保持不變,但有時確實需要更改。在 Java 中,這通常會導致一個新的 Java 介面,例如 AirlineService2,以及該介面的(新的)實現。當然,舊服務必須保留,因為可能存在尚未遷移的客戶端。

如果使用契約優先,我們可以使契約和實現之間的耦合更鬆散。這種更鬆散的耦合允許我們在一個類中實現契約的兩個版本。例如,我們可以使用 XSLT 樣式表將任何“舊式”訊息轉換為“新式”訊息。

3. 編寫契約優先的 Web 服務

本教程向您展示如何編寫契約優先的 Web 服務——即,如何開發首先從 XML Schema 或 WSDL 契約開始,然後才是 Java 程式碼的 Web 服務。Spring-WS 專注於這種開發風格,本教程應該可以幫助您入門。請注意,本教程的第一部分幾乎不包含 Spring-WS 特定資訊。它主要涉及 XML、XSD 和 WSDL。第二部分側重於使用 Spring-WS 實現此契約。

進行契約優先 Web 服務開發時最重要的事情是從 XML 的角度思考。這意味著 Java 語言概念的重要性較低。透過網路傳送的是 XML,您應該專注於此。Java 用作實現 Web 服務的語言是一個實現細節。

在本教程中,我們定義了一個由人力資源部門建立的 Web 服務。客戶端可以向此服務傳送假期請求表單以預訂假期。

3.1. 訊息

在本節中,我們重點關注傳送到 Web 服務和從 Web 服務傳送的實際 XML 訊息。我們首先確定這些訊息的外觀。

3.1.1. 假期

在場景中,我們必須處理假期請求,因此確定假期的 XML 形式是有意義的

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

假期由開始日期和結束日期組成。我們還決定使用標準ISO 8601日期格式,因為這省去了很多解析麻煩。我們還在元素中添加了名稱空間,以確保我們的元素可以在其他 XML 文件中使用。

3.1.2. 員工

場景中還有員工的概念。其 XML 形式如下

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

我們使用了與之前相同的名稱空間。如果此 <Employee/> 元素可以在其他場景中使用,那麼使用不同的名稱空間(例如 http://example.com/employees/schemas)可能更有意義。

3.1.3. 假期請求

holiday 元素和 employee 元素都可以放入 <HolidayRequest/>

<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
    <Holiday>
        <StartDate>2006-07-03</StartDate>
        <EndDate>2006-07-07</EndDate>
    </Holiday>
    <Employee>
        <Number>42</Number>
        <FirstName>Arjen</FirstName>
        <LastName>Poutsma</LastName>
    </Employee>
</HolidayRequest>

這兩個元素的順序無關緊要:<Employee/> 可以是第一個元素。重要的是所有資料都在那裡。事實上,資料是唯一重要的:我們採取資料驅動的方法。

3.2. 資料契約

現在我們已經看到了一些可以使用的 XML 資料示例,將其形式化為模式是有意義的。此資料契約定義了我們接受的訊息格式。有四種不同的方法來定義 XML 的此類契約

DTDs 的名稱空間支援有限,因此不適用於 Web 服務。Relax NG 和 Schematron 比 XML Schema 更容易。不幸的是,它們在各個平臺上的支援範圍不廣。因此,我們使用 XML Schema。

到目前為止,建立 XSD 的最簡單方法是從示例文件推斷它。任何好的 XML 編輯器或 Java IDE 都提供此功能。基本上,這些工具使用一些示例 XML 文件來生成一個模式,該模式驗證所有這些文件。最終結果當然需要打磨,但它是一個很好的起點。

使用前面描述的示例,我們最終得到以下生成的模式

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        elementFormDefault="qualified"
        targetNamespace="http://mycompany.com/hr/schemas"
        xmlns:hr="http://mycompany.com/hr/schemas">
    <xs:element name="HolidayRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="hr:Holiday"/>
                <xs:element ref="hr:Employee"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="Holiday">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="hr:StartDate"/>
                <xs:element ref="hr:EndDate"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="StartDate" type="xs:NMTOKEN"/>
    <xs:element name="EndDate" type="xs:NMTOKEN"/>
    <xs:element name="Employee">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="hr:Number"/>
                <xs:element ref="hr:FirstName"/>
                <xs:element ref="hr:LastName"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="Number" type="xs:integer"/>
    <xs:element name="FirstName" type="xs:NCName"/>
    <xs:element name="LastName" type="xs:NCName"/>
</xs:schema>

這個生成的模式可以改進。首先要注意的是,每個型別都有一個根級元素宣告。這意味著 Web 服務應該能夠接受所有這些元素作為資料。這是不可取的:我們只想接受 <HolidayRequest/>。透過刪除包裝元素標籤(從而保留型別)並內聯結果,我們可以實現這一點,如下所示

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:hr="http://mycompany.com/hr/schemas"
        elementFormDefault="qualified"
        targetNamespace="http://mycompany.com/hr/schemas">
    <xs:element name="HolidayRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Holiday" type="hr:HolidayType"/>
                <xs:element name="Employee" type="hr:EmployeeType"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:complexType name="HolidayType">
        <xs:sequence>
            <xs:element name="StartDate" type="xs:NMTOKEN"/>
            <xs:element name="EndDate" type="xs:NMTOKEN"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="EmployeeType">
        <xs:sequence>
            <xs:element name="Number" type="xs:integer"/>
            <xs:element name="FirstName" type="xs:NCName"/>
            <xs:element name="LastName" type="xs:NCName"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

該模式仍然存在一個問題:使用這樣的模式,您可以預期以下訊息會進行驗證

<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
    <Holiday>
        <StartDate>this is not a date</StartDate>
        <EndDate>neither is this</EndDate>
    </Holiday>
    PlainText Section qName:lineannotation level:4, chunks:[<, !-- ... --, >] attrs:[:]
</HolidayRequest>

顯然,我們必須確保開始日期和結束日期確實是日期。XML Schema 有一個很好的內建 date 型別我們可以使用。我們還將 NCName 更改為 string 例項。最後,我們將 <HolidayRequest/> 中的 sequence 更改為 all。這告訴 XML 解析器 <Holiday/><Employee/> 的順序不重要。我們最終的 XSD 現在看起來像以下列表

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:hr="http://mycompany.com/hr/schemas"
        elementFormDefault="qualified"
        targetNamespace="http://mycompany.com/hr/schemas">
    <xs:element name="HolidayRequest">
        <xs:complexType>
            <xs:all>
                <xs:element name="Holiday" type="hr:HolidayType"/> (1)
                <xs:element name="Employee" type="hr:EmployeeType"/> (1)
            </xs:all>
        </xs:complexType>
    </xs:element>
    <xs:complexType name="HolidayType">
        <xs:sequence>
            <xs:element name="StartDate" type="xs:date"/> (2)
            <xs:element name="EndDate" type="xs:date"/> (2)
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="EmployeeType">
        <xs:sequence>
            <xs:element name="Number" type="xs:integer"/>
            <xs:element name="FirstName" type="xs:string"/> (3)
            <xs:element name="LastName" type="xs:string"/> (3)
        </xs:sequence>
    </xs:complexType>
</xs:schema>
1 all 告訴 XML 解析器 <Holiday/><Employee/> 的順序不重要。
2 我們對 <StartDate/><EndDate/> 使用 xs:date 資料型別(由年、月、日組成)。
3 xs:string 用於名字和姓氏。

我們將此檔案儲存為 hr.xsd

3.3. 服務契約

服務契約通常表示為 WSDL 檔案。請注意,在 Spring-WS 中,不需要手動編寫 WSDL。根據 XSD 和一些約定,Spring-WS 可以為您建立 WSDL,如實現端點一節所述。本節的其餘部分演示瞭如何手動編寫 WSDL。您可能希望跳到下一節

我們從 WSDL 的標準前導開始,並透過匯入現有的 XSD。為了將模式與定義分開,我們為 WSDL 定義使用了一個單獨的名稱空間:http://mycompany.com/hr/definitions。以下列表顯示了前導

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:schema="http://mycompany.com/hr/schemas"
                  xmlns:tns="http://mycompany.com/hr/definitions"
                  targetNamespace="http://mycompany.com/hr/definitions">
    <wsdl:types>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <xsd:import namespace="http://mycompany.com/hr/schemas" schemaLocation="hr.xsd"/>
        </xsd:schema>
    </wsdl:types>

接下來,我們根據編寫的模式型別新增我們的訊息。我們只有一條訊息,即我們放入模式中的 <HolidayRequest/>

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

我們將訊息作為操作新增到埠型別中

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

該訊息完成了 WSDL 的抽象部分(即介面),並留下了具體部分。具體部分由 binding(告訴客戶端如何呼叫您剛剛定義的操作)和 service(告訴客戶端在哪裡呼叫它)組成。

新增具體部分是相當標準的。為此,請參考您之前定義的抽象部分,確保對 soap:binding 元素使用 document/literal (rpc/encoded 已棄用),為操作選擇一個 soapAction (在此示例中為 http://mycompany.com/RequestHoliday,但任何 URI 都適用),並確定您希望請求到達的 location URL (在此示例中為 http://mycompany.com/humanresources)

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:schema="http://mycompany.com/hr/schemas"
                  xmlns:tns="http://mycompany.com/hr/definitions"
                  targetNamespace="http://mycompany.com/hr/definitions">
    <wsdl:types>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <xsd:import namespace="http://mycompany.com/hr/schemas"              (1)
                schemaLocation="hr.xsd"/>
        </xsd:schema>
    </wsdl:types>
    <wsdl:message name="HolidayRequest">                                         (2)
        <wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>       (3)
    </wsdl:message>
    <wsdl:portType name="HumanResource">                                         (4)
        <wsdl:operation name="Holiday">
            <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>     (2)
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="HumanResourceBinding" type="tns:HumanResource">          (4)(5)
        <soap:binding style="document"                                           (6)
            transport="http://schemas.xmlsoap.org/soap/http"/>                   (7)
        <wsdl:operation name="Holiday">
            <soap:operation soapAction="http://mycompany.com/RequestHoliday"/>   (8)
            <wsdl:input name="HolidayRequest">
                <soap:body use="literal"/>                                       (6)
            </wsdl:input>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="HumanResourceService">
        <wsdl:port binding="tns:HumanResourceBinding" name="HumanResourcePort">  (5)
            <soap:address location="https://:8080/holidayService/"/>     (9)
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>
1 我們匯入了資料契約中定義的模式。
2 我們定義了 HolidayRequest 訊息,它在 portType 中使用。
3 HolidayRequest 型別在模式中定義。
4 我們定義了 HumanResource 埠型別,它在 binding 中使用。
5 我們定義了 HumanResourceBinding 繫結,它在 port 中使用。
6 我們使用文件/字面樣式。
7 字面量 http://schemas.xmlsoap.org/soap/http 表示 HTTP 傳輸。
8 soapAction 屬性表示將隨每個請求傳送的 SOAPAction HTTP 頭。
9 https://:8080/holidayService/ 地址是 Web 服務可以呼叫的 URL。

上面的列表顯示了最終的 WSDL。我們將在下一節中介紹如何實現生成的模式和 WSDL。

3.4. 建立專案

在本節中,我們使用 Maven 為我們建立初始專案結構。這樣做並非必需,但可以大大減少我們為設定 HolidayService 必須編寫的程式碼量。

以下命令使用 Spring-WS 原型(即專案模板)為我們建立一個 Maven Web 應用程式專案

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

上述命令建立一個名為 holidayService 的新目錄。此目錄中有一個 src/main/webapp 目錄,其中包含 WAR 檔案的根目錄。您可以在此處找到標準的 Web 應用程式部署描述符 ('WEB-INF/web.xml'),它定義了一個 Spring-WS MessageDispatcherServlet 並將所有傳入請求對映到此 servlet

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

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

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

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

</web-app>

除了上述 WEB-INF/web.xml 檔案之外,您還需要另一個 Spring-WS 特定配置檔案,名為 WEB-INF/spring-ws-servlet.xml。此檔案包含所有 Spring-WS 特定 bean,例如 EndPointsWebServiceMessageReceivers,並用於建立新的 Spring 容器。此檔案的名稱派生自伴隨的 servlet 名稱(在此示例中為 'spring-ws'),並附加了 -servlet.xml。因此,如果您定義一個名為 'dynamite'MessageDispatcherServlet,則 Spring-WS 特定配置檔案的名稱將變為 WEB-INF/dynamite-servlet.xml

(您可以在[tutorial.example.sws-conf-file]中檢視此示例的 WEB-INF/spring-ws-servlet.xml 檔案內容。)

建立專案結構後,您可以將上一節中的模式和 WSDL 放入 'WEB-INF/' 資料夾中。

3.5. 實現端點

在 Spring-WS 中,您實現端點以處理傳入的 XML 訊息。端點通常透過使用 @Endpoint 註解來註解類來建立。在此端點類中,您可以建立一個或多個處理傳入請求的方法。方法簽名可以非常靈活。您可以包含幾乎任何與傳入 XML 訊息相關的引數型別,正如我們將在本章後面解釋的那樣。

3.5.1. 處理 XML 訊息

在此示例應用程式中,我們使用 JDom 2 處理 XML 訊息。我們還使用 XPath,因為它允許我們選擇 XML JDOM 樹的特定部分,而不需要嚴格的模式一致性。

以下列表顯示了定義我們的假期端點的類

package com.mycompany.hr.ws;

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

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

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

@Endpoint                                                                                     (1)
public class HolidayEndpoint {

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

    private XPathExpression<Element> startDateExpression;

    private XPathExpression<Element> endDateExpression;

    private XPathExpression<Element> firstNameExpression;

    private XPathExpression<Element> lastNameExpression;

    private HumanResourceService humanResourceService;

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

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

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

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

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

}
1 HolidayEndpoint 使用 @Endpoint 註解。這會將此類標記為一種特殊的 @Component,適用於在 Spring-WS 中處理 XML 訊息,並使其也適合進行元件掃描。
2 HolidayEndpoint 需要 HumanResourceService 業務服務才能執行,因此我們在建構函式中注入了依賴項並使用 @Autowired 註解它。接下來,我們使用 JDOM2 API 設定 XPath 表示式。有四個表示式://hr:StartDate 用於提取 <StartDate> 文字值,//hr:EndDate 用於提取結束日期,以及兩個用於提取員工姓名。
3 @PayloadRoot 註解告訴 Spring-WS handleHolidayRequest 方法適用於處理 XML 訊息。此方法可以處理的訊息型別由註解值指示。在此示例中,它可以處理具有 HolidayRequest 本地部分和 http://mycompany.com/hr/schemas 名稱空間的 XML 元素。有關將訊息對映到端點的更多資訊在下一節中提供。
4 handleHolidayRequest(..) 方法是主要的處理方法,它會傳入來自傳入 XML 訊息的 <HolidayRequest/> 元素。@RequestPayload 註解表示 holidayRequest 引數應該對映到請求訊息的有效負載。我們使用 XPath 表示式從 XML 訊息中提取字串值,並使用 SimpleDateFormatparseData 方法)將這些值轉換為 Date 物件。有了這些值,我們就可以在業務服務上呼叫一個方法。通常,這會導致啟動資料庫事務並更改資料庫中的一些記錄。最後,我們定義了一個 void 返回型別,它告訴 Spring-WS 我們不想傳送響應訊息。如果我們需要響應訊息,我們可以返回一個 JDOM 元素來表示響應訊息的有效負載。

使用 JDOM 只是處理 XML 的選項之一。其他選項包括 DOM、dom4j、XOM、SAX 和 StAX,以及 JAXB、Castor、XMLBeans、JiBX 和 XStream 等編組技術,如下一章所述。我們選擇 JDOM 是因為它允許我們訪問原始 XML,並且它基於類(而不是像 W3C DOM 和 dom4j 那樣基於介面和工廠方法),這使得程式碼更簡潔。我們使用 XPath 是因為它比編組技術更健壯。只要我們能找到日期和名稱,我們就不需要嚴格的模式一致性。

因為我們使用 JDOM,所以我們必須向專案根目錄中的 Maven pom.xml 新增一些依賴項。以下是 POM 的相關部分

<dependencies>
    <dependency>
        <groupId>org.springframework.ws</groupId>
        <artifactId>spring-ws-core</artifactId>
        <version></version>
    </dependency>
    <dependency>
        <groupId>jdom</groupId>
        <artifactId>jdom</artifactId>
        <version>2.0.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1</version>
    </dependency>
</dependencies>

以下是我們在 spring-ws-servlet.xml Spring XML 配置檔案中使用元件掃描配置這些類的方法。我們還指示 Spring-WS 使用註解驅動的端點,使用 <sws:annotation-driven> 元素。

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:sws="http://www.springframework.org/schema/web-services"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package="com.mycompany.hr"/>

  <sws:annotation-driven/>

</beans>

3.5.2. 將訊息路由到端點

在編寫端點時,我們還使用了 @PayloadRoot 註解來指示 handleHolidayRequest 方法可以處理哪種訊息。在 Spring-WS 中,這個過程是 EndpointMapping 的職責。在這裡,我們透過使用 PayloadRootAnnotationMethodEndpointMapping 根據訊息內容路由訊息。以下列表顯示了我們之前使用的註解

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

上述示例中顯示的註解基本上意味著,每當收到具有名稱空間 http://mycompany.com/hr/schemas 和本地名稱 HolidayRequest 的 XML 訊息時,它都會路由到 handleHolidayRequest 方法。透過在配置中使用 <sws:annotation-driven> 元素,我們啟用了 @PayloadRoot 註解的檢測。在一個端點中擁有多個相關處理方法(每個方法處理不同的 XML 訊息)是可能(並且很常見)的。

還有其他將端點對映到 XML 訊息的方法,如下一章所述。

3.5.3. 提供服務和存根實現

現在我們有了端點,我們需要 HumanResourceService 及其實現供 HolidayEndpoint 使用。以下列表顯示了 HumanResourceService 介面

package com.mycompany.hr.service;

import java.util.Date;

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

為了教程目的,我們使用 HumanResourceService 的簡單存根實現

package com.mycompany.hr.service;

import java.util.Date;

import org.springframework.stereotype.Service;

@Service                                                                 (1)
public class StubHumanResourceService implements HumanResourceService {
    public void bookHoliday(Date startDate, Date endDate, String name) {
        System.out.println("Booking holiday for [" + startDate + "-" + endDate + "] for [" + name + "] ");
    }
}
1 StubHumanResourceService 使用 @Service 註解。這會將此類標記為業務外觀,使其成為 HolidayEndpoint@Autowired 注入的候選物件。

3.6. 釋出 WSDL

最後,我們需要釋出 WSDL。如服務契約中所述,我們不需要自己編寫 WSDL。Spring-WS 可以根據一些約定生成一個。以下是我們定義生成方式的方法

<sws:dynamic-wsdl id="holiday"                                (1)
    portTypeName="HumanResource"                              (3)
    locationUri="/holidayService/"                            (4)
    targetNamespace="http://mycompany.com/hr/definitions">    (5)
  <sws:xsd location="/WEB-INF/hr.xsd"/>                       (2)
</sws:dynamic-wsdl>
1 id 確定可以檢索 WSDL 的 URL。在此示例中,idholiday,這意味著 WSDL 可以作為 holiday.wsdl 在 servlet 上下文中檢索。完整 URL 是 https://:8080/holidayService/holiday.wsdl
2 接下來,我們將 WSDL 埠型別設定為 HumanResource
3 我們設定了可以訪問服務的 location:/holidayService/。我們使用相對 URI,並指示框架將其動態轉換為絕對 URI。因此,如果服務部署到不同的上下文,我們不必手動更改 URI。有關更多資訊,請參見名為“自動 WSDL 暴露”的章節。為了使 location 轉換生效,我們需要在 web.xml 中向 spring-ws servlet 新增一個 init 引數(如下面的列表所示)。
4 我們為 WSDL 定義本身定義了目標名稱空間。設定此屬性並非必需。如果未設定,WSDL 將具有與 XSD 模式相同的名稱空間。
5 xsd 元素引用了我們在資料契約中定義的人力資源模式。我們將模式放置在應用程式的 WEB-INF 目錄中。

以下列表顯示瞭如何新增 init 引數

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

您可以使用 mvn install 建立 WAR 檔案。如果您部署應用程式(到 Tomcat、Jetty 等),並將瀏覽器指向此位置,您將看到生成的 WSDL。此 WSDL 已準備好供客戶端使用,例如 soapUI 或其他 SOAP 框架。

本教程到此結束。教程程式碼可以在 Spring-WS 的完整發行版中找到。如果您想繼續,請檢視發行版中包含的 echo 示例應用程式。之後,檢視 airline 示例,它稍微複雜一些,因為它使用了 JAXB、WS-Security、Hibernate 和事務性服務層。最後,您可以閱讀參考文件的其餘部分。

II. 參考

本參考文件部分詳細介紹了構成 Spring Web Services 的各種元件。這包括討論客戶端和伺服器端 WS 共享部分的一章、專門討論編寫伺服器端 Web 服務細節的章節、關於在客戶端使用 Web 服務的章節,以及關於使用WS-Security的章節。

4. 共享元件

本章探討了客戶端和伺服器端 Spring-WS 開發之間共享的元件。這些介面和類代表了 Spring-WS 的構建塊,因此您需要了解它們的功能,即使您不直接使用它們。

4.1. Web 服務訊息

本節描述了 Spring-WS 使用的訊息和訊息工廠。

4.1.1. WebServiceMessage

Spring Web Services 的核心介面之一是 WebServiceMessage。此介面表示協議無關的 XML 訊息。該介面包含提供對訊息有效負載訪問的方法,形式為 javax.xml.transform.Sourcejavax.xml.transform.ResultSourceResult 是標記介面,表示對 XML 輸入和輸出的抽象。具體實現包裝了各種 XML 表示,如下表所示

Source 或 Result 實現 封裝的 XML 表示

javax.xml.transform.dom.DOMSource

org.w3c.dom.Node

javax.xml.transform.dom.DOMResult

org.w3c.dom.Node

javax.xml.transform.sax.SAXSource

org.xml.sax.InputSourceorg.xml.sax.XMLReader

javax.xml.transform.sax.SAXResult

org.xml.sax.ContentHandler

javax.xml.transform.stream.StreamSource

java.io.File, java.io.InputStream, 或 java.io.Reader

javax.xml.transform.stream.StreamResult

java.io.File, java.io.OutputStream, 或 java.io.Writer

除了從有效負載讀取和寫入有效負載之外,Web 服務訊息還可以將自身寫入輸出流。

4.1.2. SoapMessage

SoapMessageWebServiceMessage 的子類。它包含 SOAP 特定的方法,例如獲取 SOAP 頭、SOAP 錯誤等。通常,您的程式碼不應依賴於 SoapMessage,因為 SOAP 正文的內容(訊息的有效負載)可以透過使用 WebServiceMessage 中的 getPayloadSource()getPayloadResult() 獲取。只有在需要執行 SOAP 特定操作(例如新增頭、獲取附件等)時,才需要將 WebServiceMessage 轉換為 SoapMessage

4.1.3. 訊息工廠

具體訊息實現由 WebServiceMessageFactory 建立。此工廠可以建立空訊息或從輸入流讀取訊息。WebServiceMessageFactory 有兩個具體實現。一個基於 SAAJ,即 Java 的帶附件 SOAP API。另一個基於 Axis 2 的 AXIOM (AXis Object Model)。

SaajSoapMessageFactory

SaajSoapMessageFactory 使用 Java 的帶附件 SOAP API (SAAJ) 建立 SoapMessage 實現。SAAJ 是 J2EE 1.4 的一部分,因此它應該在大多數現代應用程式伺服器下都受支援。以下是常見應用程式伺服器提供的 SAAJ 版本概述

應用程式伺服器 SAAJ 版本

BEA WebLogic 8

1.1

BEA WebLogic 9

1.1/1.21

IBM WebSphere 6

1.2

SUN Glassfish 1

1.3

1Weblogic 9 在 SAAJ 1.2 實現中存在一個已知錯誤:它實現了所有 1.2 介面,但在呼叫時會丟擲 UnsupportedOperationException。Spring Web Services 有一個解決方法:它在 WebLogic 9 上執行時使用 SAAJ 1.1。

此外,Java SE 6 包含 SAAJ 1.3。您可以按如下方式配置 SaajSoapMessageFactory

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
SAAJ 基於 DOM,即文件物件模型。這意味著所有 SOAP 訊息都儲存在記憶體中。對於較大的 SOAP 訊息,這可能無法提供高效能。在這種情況下,AxiomSoapMessageFactory 可能更適用。
AxiomSoapMessageFactory

AxiomSoapMessageFactory 使用 AXis 2 物件模型 (AXIOM) 建立 SoapMessage 實現。AXIOM 基於 StAX,即 XML 流 API。StAX 提供了一種基於拉取的機制來讀取 XML 訊息,這對於較大的訊息可能更高效。

為了提高 AxiomSoapMessageFactory 上的讀取效能,您可以將 payloadCaching 屬性設定為 false(預設為 true)。這樣做會導致 SOAP 主體的內容直接從套接字流讀取。啟用此設定後,有效負載只能讀取一次。這意味著您必須確保訊息的任何預處理(日誌記錄或其他工作)都不會消耗它。

您可以按如下方式使用 AxiomSoapMessageFactory

<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory">
    <property name="payloadCaching" value="true"/>
</bean>

除了有效負載快取之外,AXIOM 還支援完整的流式訊息,如 StreamingWebServiceMessage 中定義的那樣。這意味著您可以直接在響應訊息上設定有效負載,而不是將其寫入 DOM 樹或緩衝區。

當處理程式方法返回 JAXB2 支援的物件時,將使用 AXIOM 的完整流式傳輸。它會自動將此編組物件設定為響應訊息,並在響應傳出時將其寫入傳出套接字流。

有關完整流式傳輸的更多資訊,請參閱 StreamingWebServiceMessageStreamingPayload 的類級 Javadoc。

SOAP 1.1 或 1.2

SaajSoapMessageFactoryAxiomSoapMessageFactory 都有一個 soapVersion 屬性,您可以在其中注入 SoapVersion 常量。預設情況下,版本為 1.1,但您可以將其設定為 1.2

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-2.0.xsd">

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory">
        <property name="soapVersion">
            <util:constant static-field="org.springframework.ws.soap.SoapVersion.SOAP_12"/>
        </property>
    </bean>

</beans>

在前面的示例中,我們定義了一個 SaajSoapMessageFactory,它只接受 SOAP 1.2 訊息。

儘管兩個版本的 SOAP 格式非常相似,但 1.2 版本與 1.1 不向後相容,因為它使用不同的 XML 名稱空間。SOAP 1.1 和 1.2 之間的其他主要區別包括故障結構的不同以及 SOAPAction HTTP 標頭實際上已棄用,儘管它們仍然有效。

關於 SOAP 版本號(或通常的 WS-* 規範版本號)需要注意的一點是,規範的最新版本通常不是最受歡迎的版本。對於 SOAP,這意味著(目前)最佳版本是 1.1。1.2 版本將來可能會更受歡迎,但 1.1 目前是最安全的選擇。

4.1.4. MessageContext

通常,訊息成對出現:一個請求和一個響應。請求在客戶端建立,透過某種傳輸傳送到伺服器端,伺服器端生成響應。此響應傳送回客戶端,並在客戶端讀取。

在 Spring Web Services 中,此類對話包含在 MessageContext 中,該上下文具有獲取請求和響應訊息的屬性。在客戶端,訊息上下文由WebServiceTemplate建立。在伺服器端,訊息上下文從傳輸特定的輸入流讀取。例如,在 HTTP 中,它從 HttpServletRequest 讀取,響應寫入 HttpServletResponse

4.2. TransportContext

SOAP 協議的關鍵特性之一是它試圖實現傳輸無關性。這就是為什麼 Spring-WS 不支援透過 HTTP 請求 URL 而是透過訊息內容將訊息對映到端點。

然而,有時需要訪問底層傳輸,無論是在客戶端還是伺服器端。為此,Spring Web Services 提供了 TransportContext。傳輸上下文允許訪問底層 WebServiceConnection,通常在伺服器端是 HttpServletConnection,在客戶端是 HttpUrlConnectionCommonsHttpConnection。例如,您可以在伺服器端端點或攔截器中獲取當前請求的 IP 地址

TransportContext context = TransportContextHolder.getTransportContext();
HttpServletConnection connection = (HttpServletConnection )context.getConnection();
HttpServletRequest request = connection.getHttpServletRequest();
String ipAddress = request.getRemoteAddr();

4.3. 使用 XPath 處理 XML

處理 XML 的最佳方法之一是使用 XPath。引用[effective-xml],第 35 項

XPath 是一種第四代宣告式語言,允許您指定要處理的節點,而無需精確指定處理器如何導航到這些節點。XPath 的資料模型設計得非常好,可以準確支援幾乎所有開發人員對 XML 的需求。例如,它合併所有相鄰文字,包括 CDATA 部分中的文字,允許計算跳過註釋和處理指令幷包含子元素和後代元素文字的值,並要求解析所有外部實體引用。實際上,XPath 表示式在面對輸入文件中意外但可能不重要的更改時往往更加健壯。
— Elliotte Rusty Harold

Spring Web Services 提供了兩種在應用程式中使用 XPath 的方式:更快的 XPathExpression 或更靈活的 XPathTemplate

4.3.1. XPathExpression

XPathExpression 是對已編譯 XPath 表示式的抽象,例如 Java 5 javax.xml.xpath.XPathExpression 介面或 Jaxen XPath 類。要在應用程式上下文中構建表示式,您可以使用 XPathExpressionFactoryBean。以下示例使用此工廠 bean

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="nameExpression" class="org.springframework.xml.xpath.XPathExpressionFactoryBean">
        <property name="expression" value="/Contacts/Contact/Name"/>
    </bean>

    <bean id="myEndpoint" class="sample.MyXPathClass">
        <constructor-arg ref="nameExpression"/>
    </bean>

</beans>

上述表示式不使用名稱空間,但我們可以透過使用工廠 bean 的 namespaces 屬性來設定它們。表示式可以在程式碼中按如下方式使用

package sample;

public class MyXPathClass {

    private final XPathExpression nameExpression;

    public MyXPathClass(XPathExpression nameExpression) {
        this.nameExpression = nameExpression;
    }

    public void doXPath(Document document) {
        String name = nameExpression.evaluateAsString(document.getDocumentElement());
        System.out.println("Name: " + name);
    }

}

為了更靈活的方法,您可以使用 NodeMapper,它類似於 Spring 的 JDBC 支援中的 RowMapper。以下示例顯示瞭如何使用它

package sample;

public class MyXPathClass  {

   private final XPathExpression contactExpression;

   public MyXPathClass(XPathExpression contactExpression) {
      this.contactExpression = contactExpression;
   }

   public void doXPath(Document document) {
      List contacts = contactExpression.evaluate(document,
        new NodeMapper() {
           public Object mapNode(Node node, int nodeNum) throws DOMException {
              Element contactElement = (Element) node;
              Element nameElement = (Element) contactElement.getElementsByTagName("Name").item(0);
              Element phoneElement = (Element) contactElement.getElementsByTagName("Phone").item(0);
              return new Contact(nameElement.getTextContent(), phoneElement.getTextContent());
           }
        });
      PlainText Section qName; // do something with the list of Contact objects
   }
}

與 Spring JDBC 的 RowMapper 中的行對映類似,每個結果節點都使用匿名內部類進行對映。在此示例中,我們建立一個 Contact 物件,我們稍後會使用它。

4.3.2. XPathTemplate

XPathExpression 僅允許您評估單個預編譯表示式。一種更靈活(儘管更慢)的替代方案是 XpathTemplate。此類遵循 Spring 中常用的模板模式(JdbcTemplateJmsTemplate 等)。以下列表顯示了一個示例

package sample;

public class MyXPathClass {

    private XPathOperations template = new Jaxp13XPathTemplate();

    public void doXPath(Source source) {
        String name = template.evaluateAsString("/Contacts/Contact/Name", request);
        // do something with name
    }

}

4.4. 訊息日誌和跟蹤

在開發或除錯 Web 服務時,檢視(SOAP)訊息到達或傳送之前的內容非常有用。Spring Web Services 透過標準 Commons Logging 介面提供此功能。

請務必使用 Commons Logging 1.1 或更高版本。早期版本存在類載入問題,並且不與 Log4J TRACE 級別整合。

要記錄所有伺服器端訊息,請將 org.springframework.ws.server.MessageTracing 日誌級別設定為 DEBUGTRACE。在 DEBUG 級別,僅記錄有效負載根元素。在 TRACE 級別,記錄整個訊息內容。如果您只想記錄已傳送的訊息,請使用 org.springframework.ws.server.MessageTracing.sent 記錄器。同樣,您可以使用 org.springframework.ws.server.MessageTracing.received 記錄器來記錄僅接收到的訊息。

在客戶端,存在類似的日誌記錄器:org.springframework.ws.client.MessageTracing.sentorg.springframework.ws.client.MessageTracing.received

以下是 log4j.properties 配置檔案的示例,它記錄客戶端傳送訊息的全部內容,以及客戶端接收訊息的僅有效負載根元素。在伺服器端,記錄傳送和接收訊息的有效負載根

log4j.rootCategory=INFO, stdout
log4j.logger.org.springframework.ws.client.MessageTracing.sent=TRACE
log4j.logger.org.springframework.ws.client.MessageTracing.received=DEBUG

log4j.logger.org.springframework.ws.server.MessageTracing=DEBUG

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p [%c{3}] %m%n

在此配置下,典型輸出為

TRACE [client.MessageTracing.sent] Sent request [<SOAP-ENV:Envelope xmlns:SOAP-ENV="...
DEBUG [server.MessageTracing.received] Received request [SaajSoapMessage {http://example.com}request] ...
DEBUG [server.MessageTracing.sent] Sent response [SaajSoapMessage {http://example.com}response] ...
DEBUG [client.MessageTracing.received] Received response [SaajSoapMessage {http://example.com}response] ...

5. 使用 Spring-WS 建立 Web 服務

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

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

以下序列圖顯示了 MessageDispatcher 的處理和分發流程

sequence

MessageDispatcher 設定為可用並且針對該特定排程程式傳入請求時,MessageDispatcher 開始處理請求。以下過程描述了 MessageDispatcher 如何處理請求

  1. 搜尋配置的 EndpointMapping(s) 以查詢適當的端點。如果找到端點,則呼叫與端點關聯的呼叫鏈(預處理器、後處理器和端點)以建立響應。

  2. 為端點找到一個合適的介面卡。MessageDispatcher 將委託給此介面卡來呼叫端點。

  3. 如果返回響應,則將其傳送出去。如果沒有返回響應(這可能是由於預處理器或後處理器攔截請求,例如出於安全原因),則不傳送響應。

在請求處理期間丟擲的異常將被應用程式上下文中宣告的任何端點異常解析器捕獲。使用這些異常解析器允許您在丟擲此類異常時定義自定義行為(例如返回 SOAP 錯誤)。

MessageDispatcher 有幾個屬性用於設定端點介面卡、對映異常解析器。但是,設定這些屬性不是必需的,因為排程程式會自動檢測應用程式上下文中註冊的所有型別。您只有在需要覆蓋檢測時才應設定這些屬性。

訊息排程器操作的是訊息上下文,而不是傳輸特定的輸入流和輸出流。因此,傳輸特定的請求需要讀入 MessageContext。對於 HTTP,這是透過 WebServiceMessageReceiverHandlerAdapter(一個 Spring Web HandlerInterceptor)完成的,以便可以將 MessageDispatcher 連線到標準的 DispatcherServlet 中。但是,有一種更方便的方法可以做到這一點,如MessageDispatcherServlet中所示。

5.2. 傳輸

Spring Web Services 支援多種傳輸協議。最常見的是 HTTP 傳輸,為此提供了一個自定義 servlet,但您也可以透過 JMS 甚至電子郵件傳送訊息。

5.2.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,例如端點、編組器等。

作為 web.xml 的替代方案,如果您在 Servlet 3+ 環境中執行,您可以以程式設計方式配置 Spring-WS。為此,Spring-WS 提供了許多抽象基類,它們擴充套件了 Spring Framework 中找到的 WebApplicationInitializer 介面。如果您還使用 @Configuration 類來定義 bean,則應該擴充套件 AbstractAnnotationConfigMessageDispatcherServletInitializer

public class MyServletInitializer
    extends AbstractAnnotationConfigMessageDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{MyRootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MyEndpointConfig.class};
    }

}

在前面的示例中,我們告訴 Spring 端點 bean 定義可以在 MyEndpointConfig 類(這是一個 @Configuration 類)中找到。其他 bean 定義(通常是服務、儲存庫等)可以在 MyRootConfig 類中找到。預設情況下,AbstractAnnotationConfigMessageDispatcherServletInitializer 將 servlet 對映到兩個模式:/services*.wsdl,儘管您可以透過覆蓋 getServletMappings() 方法來更改此設定。有關 MessageDispatcherServlet 程式設計配置的更多詳細資訊,請參閱 AbstractMessageDispatcherServletInitializerAbstractAnnotationConfigMessageDispatcherServletInitializer 的 Javadoc。

自動 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="orders.wsdl"/>

或者,它也可以是 @Configuration 類中的 @Bean 方法

@Bean
public SimpleWsdl11Definition orders() {
	return new SimpleWsdl11Definition(new ClassPathResource("orders.wsdl"));
}

您可以透過以下形式的 URL 訪問類路徑中 orders.wsdl 檔案中定義的 WSDL (請根據需要替換主機、埠和 servlet 上下文路徑)

https://:8080/spring-ws/orders.wsdl
所有 WsdlDefinition bean 定義都由 MessageDispatcherServlet 在其 bean 名稱下公開,字尾為 `.wsdl`。因此,如果 bean 名稱為 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>

如果您使用 AbstractAnnotationConfigMessageDispatcherServletInitializer,啟用轉換就像覆蓋 isTransformWsdlLocations() 方法以返回 true 一樣簡單。

請查閱 WsdlDefinitionHandlerAdapter 類的類級 Javadoc,以瞭解整個轉換過程的更多資訊。

作為手動編寫 WSDL 並使用 <static-wsdl> 暴露它的替代方案,Spring Web Services 還可以從 XSD 模式生成 WSDL。這是釋出 WSDL中顯示的方法。以下應用程式上下文片段顯示瞭如何建立此類動態 WSDL 檔案

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

或者,您可以使用 Java @Bean 方法

@Bean
public DefaultWsdl11Definition orders() {
    DefaultWsdl11Definition definition = new DefaultWsdl11Definition();
    definition.setPortTypeName("Orders");
    definition.setLocationUri("https://:8080/ordersService/");
    definition.setSchema(new SimpleXsdSchema(new ClassPathResource("echo.xsd")));

    return definition;
}

<dynamic-wsdl> 元素依賴於 DefaultWsdl11Definition 類。這個定義類使用 org.springframework.ws.wsdl.wsdl11.provider 包中的 WSDL 提供程式和 ProviderBasedWsdl4jDefinition 類在首次請求時生成 WSDL。請參閱這些類的類級 Javadoc,以瞭解在必要時如何擴充套件此機制。

DefaultWsdl11Definition(因此,<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 中。這大大簡化了模式的部署,同時仍然可以單獨編輯它們。

儘管在執行時從 XSD 生成 WSDL 可能很方便,但這種方法存在一些缺點。首先,儘管我們努力保持 WSDL 生成過程在不同版本之間的一致性,但它仍然有可能(略微)發生變化。其次,生成速度有點慢,儘管一旦生成,WSDL 會被快取以供以後引用。

因此,您應該僅在專案的開發階段使用 <dynamic-wsdl>。我們建議使用瀏覽器下載生成的 WSDL,將其儲存在專案中,並使用 <static-wsdl> 公開它。這是唯一能真正確保 WSDL 不隨時間變化的方法。

5.2.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.method.annotation.RequestMappingHandlerAdapter"/>

</beans>

請注意,透過顯式新增 WebServiceMessageReceiverHandlerAdapter,排程程式 servlet 不會載入預設介面卡,並且無法處理標準 Spring-MVC @Controllers。因此,我們將 RequestMappingHandlerAdapter 新增到末尾。

以類似的方式,您可以連線 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.2.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>

5.2.4. 電子郵件傳輸

除了 HTTP 和 JMS,Spring Web Services 還提供伺服器端電子郵件處理。此功能透過 MailMessageReceiver 類提供。該類監控 POP3 或 IMAP 資料夾,將電子郵件轉換為 WebServiceMessage,並使用 SMTP 傳送任何響應。您可以透過 storeUri 配置主機名,該屬性指示要監控請求的郵件資料夾(通常是 POP3 或 IMAP 資料夾),並透過 transportUri 配置主機名,該屬性指示用於傳送響應的伺服器(通常是 SMTP 伺服器)。

您可以使用可插拔策略:MonitoringStrategy 來配置 MailMessageReceiver 如何監控傳入訊息。預設情況下,使用輪詢策略,每五分鐘輪詢一次傳入資料夾以查詢新訊息。您可以透過設定策略上的 pollingInterval 屬性來更改此間隔。預設情況下,所有 MonitoringStrategy 實現都會刪除已處理的訊息。您可以透過設定 deleteMessages 屬性來更改此設定。

作為效率較低的輪詢方法的替代方案,有一種使用 IMAP IDLE 的監控策略。IDLE 命令是 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.2.5. 嵌入式 HTTP 伺服器傳輸

Spring Web Services 提供了一個基於 Sun JRE 1.6 HTTP 伺服器的傳輸。嵌入式 HTTP 伺服器是一個獨立伺服器,配置簡單。它為傳統的 Servlet 容器提供了一種更輕量級的替代方案。

使用嵌入式 HTTP 伺服器時,您不需要外部部署描述符(web.xml)。您只需定義一個伺服器例項並將其配置為處理傳入請求。Core 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.2.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.2.7. MTOM

MTOM 是用於向 Web 服務傳送和接收二進位制資料的機制。您可以透過 MTOM 示例瞭解如何使用 Spring WS 實現此功能。

5.3. 端點

端點是 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 返回型別,表示不傳送響應訊息。有關端點方法的更多資訊,請參閱 @Endpoint 處理方法
4 getOrder 方法接受一個 OrderRequest(也用 @RequestPayload 註解)作為引數。此引數是 JAXB2 支援的物件(它用 @XmlRootElement 註解)。這意味著訊息的有效載荷作為未解組的物件傳遞給此方法。SoapHeader 型別也作為引數給出。在呼叫時,此引數包含請求訊息的 SOAP 頭。該方法也用 @ResponsePayload 註解,表示返回值(Order)用作響應訊息的有效載荷。有關端點方法的更多資訊,請參閱 @Endpoint 處理方法
5 此端點的兩個處理方法都使用 @PayloadRoot 標記,指示方法可以處理哪種請求訊息:getOrder 方法針對有效載荷根元素的區域性名稱為 orderRequest 且名稱空間 URI 為 http://samples 的請求呼叫。order 方法針對區域性名稱為 order 的請求呼叫。有關 @PayloadRoot 的更多資訊,請參閱 端點對映

要啟用對 @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.xsd
    http://www.springframework.org/schema/web-services
      http://www.springframework.org/schema/web-services/web-services.xsd">

  *<sws:annotation-driven />

</beans>

或者,如果您使用 @Configuration 類而不是 Spring XML,則可以使用 @EnableWs 註解您的配置類

@EnableWs
@Configuration
public class EchoConfig {

    // @Bean definitions go here

}

要自定義 @EnableWs 配置,您可以實現 WsConfigurer,或者更好地擴充套件 WsConfigurerAdapter

@Configuration
@EnableWs
@ComponentScan(basePackageClasses = { MyConfiguration.class })
public class MyConfiguration extends WsConfigurerAdapter {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    interceptors.add(new MyInterceptor());
  }

  @Override
  public void addArgumentResolvers(List<MethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(new MyArgumentResolver());
  }

  // More overridden methods ...
}

在接下來的幾節中,將更詳細地描述 @Endpoint 程式設計模型。

端點,像任何其他 Spring Bean 一樣,預設情況下被作用域為單例。也就是說,每個容器只建立一個 bean 定義例項。作為單例意味著多個執行緒可以同時使用它,因此端點必須是執行緒安全的。如果您想使用不同的作用域,例如原型,請參閱 Spring 參考文件
Note that all abstract base classes provided in Spring-WS are thread safe, unless otherwise indicated in the class-level Javadoc.

5.3.1. @Endpoint 處理方法

為了讓端點實際處理傳入的 XML 訊息,它需要有一個或多個處理方法。處理方法可以接受各種引數和返回型別。然而,它們通常有一個引數包含訊息有效載荷,並返回響應訊息的有效載荷(如果有的話)。本節介紹支援哪些引數和返回型別。

為了指示方法可以處理哪種訊息,方法通常使用 @PayloadRoot@SoapAction 註解進行註解。您可以在 端點對映 中瞭解有關這些註解的更多資訊。

以下示例顯示了一個處理方法

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

order 方法接受一個 Element(用 @RequestPayload 註解)作為引數。這意味著訊息的有效載荷作為 DOM 元素傳遞給此方法。該方法具有 void 返回型別,表示不傳送響應訊息。

處理方法引數

處理方法通常有一個或多個引數,這些引數引用傳入 XML 訊息的各個部分。最常見的是,處理方法有一個對映到訊息有效載荷的單個引數,但它也可以對映到請求訊息的其他部分,例如 SOAP 頭。本節描述您可以在處理方法簽名中使用的引數。

要將引數對映到請求訊息的有效載荷,您需要使用 @RequestPayload 註解此引數。此註解告訴 Spring-WS 該引數需要繫結到請求有效載荷。

下表描述了支援的引數型別。它顯示了支援的型別,引數是否應使用 @RequestPayload 註解,以及任何附加說明。

名稱 支援的引數型別 @RequestPayload 是否必需? 附加說明

TrAX

javax.xml.transform.Source 及其子介面(DOMSourceSAXSourceStreamSourceStAXSource

預設啟用。

W3C DOM

org.w3c.dom.Element

預設啟用

dom4j

org.dom4j.Element

當 dom4j 位於類路徑上時啟用。

JDOM

org.jdom.Element

當 JDOM 位於類路徑上時啟用。

XOM

nu.xom.Element

當 XOM 位於類路徑上時啟用。

StAX

javax.xml.stream.XMLStreamReaderjavax.xml.stream.XMLEventReader

當 StAX 位於類路徑上時啟用。

XPath

任何 boolean、double、Stringorg.w3c.Nodeorg.w3c.dom.NodeList,或者可以由 Spring 轉換服務String 轉換的型別,並且用 @XPathParam 註解。

預設啟用,請參閱名為 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 位於類路徑上時啟用。

OXM

Spring OXM Unmarshaller 支援的任何型別。

當指定 <sws:annotation-driven/>unmarshaller 屬性時啟用。

接下來的幾個示例展示了可能的方法簽名。以下方法使用請求訊息的有效載荷作為 DOM org.w3c.dom.Element 呼叫

public void handle(@RequestPayload Element element)

以下方法使用請求訊息的有效載荷作為 javax.xml.transform.dom.DOMSource 呼叫。header 引數繫結到請求訊息的 SOAP 頭。

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

以下方法使用請求訊息的有效載荷解組到 MyJaxb2Object(用 @XmlRootElement 註解)中呼叫。訊息的有效載荷也作為 DOM Element 給出。整個訊息上下文作為第三個引數傳遞。

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

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

@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 轉換服務String 轉換的型別。

處理方法返回型別

要傳送響應訊息,處理需要指定返回型別。如果不需要響應訊息,方法可以宣告 void 返回型別。最常見的是,返回型別用於建立響應訊息的有效載荷。但是,您也可以對映到響應訊息的其他部分。本節描述您可以在處理方法簽名中使用的返回型別。

要將返回值對映到響應訊息的有效載荷,您需要使用 @ResponsePayload 註解該方法。此註解告訴 Spring-WS 返回值需要繫結到響應有效載荷。

下表描述了支援的返回型別。它顯示了支援的型別,引數是否應使用 @ResponsePayload 註解,以及任何附加說明。

名稱 支援的返回型別 @ResponsePayload 是否必需? 附加說明

無響應

void

預設啟用。

TrAX

javax.xml.transform.Source 及其子介面(DOMSourceSAXSourceStreamSourceStAXSource

預設啟用。

W3C DOM

org.w3c.dom.Element

預設啟用

dom4j

org.dom4j.Element

當 dom4j 位於類路徑上時啟用。

JDOM

org.jdom.Element

當 JDOM 位於類路徑上時啟用。

XOM

nu.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.4. 端點對映

端點對映負責將傳入訊息對映到適當的端點。一些端點對映預設啟用,例如 PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping。但是,我們首先需要檢查 EndpointMapping 的一般概念。

一個 EndpointMapping 交付一個 EndpointInvocationChain,其中包含與傳入請求匹配的端點,並且可能還包含應用於請求和響應的端點攔截器列表。當請求到來時,MessageDispatcher 將其交給端點對映,讓它檢查請求並生成適當的 EndpointInvocationChain。然後 MessageDispatcher 呼叫鏈中的端點和任何攔截器。

可配置的端點對映概念非常強大,它可以選擇性地包含攔截器(反過來,攔截器可以操作請求、響應或兩者)。許多支援功能可以內建到自定義 EndpointMapping 實現中。例如,自定義端點對映不僅可以根據訊息內容選擇端點,還可以根據特定的 SOAP 頭(或實際上是多個 SOAP 頭)選擇端點。

大多數端點對映繼承自 AbstractEndpointMapping,它提供了一個“攔截器”屬性,即要使用的攔截器列表。EndpointInterceptor攔截請求——EndpointInterceptor 介面中討論。此外,還有 defaultEndpoint,它是當此端點對映未導致匹配端點時使用的預設端點。

端點中所述,@Endpoint 樣式允許您在一個端點類中處理多個請求。這是 MethodEndpointMapping 的職責。此對映決定了應為傳入請求訊息呼叫哪個方法。

有兩種端點對映可以將請求定向到方法:PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping。您可以透過在應用程式上下文中使用 <sws:annotation-driven/> 啟用這兩種方法。

PayloadRootAnnotationMethodEndpointMapping 使用 @PayloadRoot 註解,以及 localPartnamespace 元素,以特定限定名稱標記方法。每當訊息以該限定名稱作為有效載荷根元素傳入時,就會呼叫該方法。有關示例,請參閱上文

或者,SoapActionAnnotationMethodEndpointMapping 使用 @SoapAction 註解來標記具有特定 SOAP 動作的方法。每當訊息帶有此 SOAPAction 頭傳入時,就會呼叫該方法。

5.4.1. WS-Addressing

WS-Addressing 指定了一種傳輸無關的路由機制。它基於 ToAction SOAP 頭,分別指示 SOAP 訊息的目標和意圖。此外,WS-Addressing 允許您定義返回地址(用於正常訊息和故障)和唯一訊息識別符號,可用於關聯。有關 WS-Addressing 的更多資訊,請參閱 https://en.wikipedia.org/wiki/WS-Addressing。以下示例顯示了一個 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

使用 AnnotationActionEndpointMapping

AnnotationActionEndpointMapping 類似於 SoapActionAnnotationMethodEndpointMapping,但使用 WS-Addressing 頭而不是 SOAP Action 傳輸頭。

要使用 AnnotationActionEndpointMapping,請使用 @Action 註解處理方法,類似於 @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 上一樣。請參閱 URI 和傳輸

5.4.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 引用。

當您使用 @Configuration 類時,您可以從 WsConfigurerAdapter 擴充套件以新增攔截器

@Configuration
@EnableWs
public class MyWsConfiguration extends WsConfigurerAdapter {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    interceptors.add(new MyPayloadRootInterceptor());
  }

}

攔截器必須實現 org.springframework.ws.server 包中的 EndpointInterceptor 介面。此介面定義了三個方法,一個用於在實際端點處理之前處理請求訊息,一個用於處理正常響應訊息,一個用於處理故障訊息。後兩個方法在端點處理之後呼叫。這三個方法應提供足夠的靈活性來執行各種預處理和後處理。

攔截器上的 handleRequest(..) 方法返回一個布林值。您可以使用此方法中斷或繼續呼叫鏈的處理。當此方法返回 true 時,端點處理鏈將繼續。當它返回 false 時,MessageDispatcher 將此解釋為攔截器本身已處理好事情,並且不會繼續處理呼叫鏈中的其他攔截器和實際端點。handleResponse(..)handleFault(..) 方法也具有布林返回值。當這些方法返回 false 時,響應將不會發送回客戶端。

有許多標準的 EndpointInterceptor 實現可用於您的 Web 服務。此外,還有 XwsSecurityInterceptor,在 XwsSecurityInterceptor 中描述。

PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor

開發 Web 服務時,記錄傳入和傳出的 XML 訊息會很有用。Spring WS 透過 PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor 類簡化了此操作。前者僅將訊息的有效負載記錄到 Commons Logging Log。後者記錄整個 SOAP 包絡,包括 SOAP 頭。以下示例顯示瞭如何在端點對映中定義 PayloadLoggingInterceptor

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

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

您也可以像前面描述的那樣,為 PayloadLoggingInterceptor 使用 WsConfigurerAdapter 方法。

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>

當然,您也可以像前面描述的那樣,為 PayloadValidatingInterceptor 使用 WsConfigurerAdapter 方法。

使用 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 轉換響應訊息。請注意,由於端點攔截器在端點對映級別註冊,您可以建立一個適用於“舊樣式”訊息的端點對映,並將攔截器新增到該對映中。因此,轉換僅適用於這些“舊樣式”訊息。

您也可以像前面描述的那樣,為 PayloadTransformingInterceptor 使用 WsConfigurerAdapter 方法。

5.5. 異常處理

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

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

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

5.5.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.5.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.6. 伺服器端測試

在測試您的 Web 服務端點時,您有兩種可能的方法

  • 編寫單元測試,您為端點提供(模擬)引數以供其使用。

    這種方法的優點是很容易實現(特別是對於用 @Endpoint 註解的類)。缺點是您沒有真正測試透過網路傳送的 XML 訊息的確切內容。

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

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

5.6.1. 編寫伺服器端整合測試

Spring Web Services 2.0 引入了對建立端點整合測試的支援。在這種上下文中,端點是處理(SOAP)訊息的類(參見端點)。

整合測試支援位於 org.springframework.ws.test.server 包中。該包中的核心類是 MockWebServiceClient。其基本思想是,該客戶端建立一個請求訊息,然後將其傳送到在標準 MessageDispatcherServlet 應用程式上下文(參見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 中的程式碼完成功能來指導您完成設定模擬伺服器的過程。
另請注意,您可以在單元測試中依賴 Spring Web Services 中提供的標準日誌記錄功能。有時,檢查請求或響應訊息以找出特定測試失敗的原因可能會很有用。有關更多資訊,請參閱訊息日誌記錄和跟蹤

例如,考慮以下 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 註解。請參閱 端點
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 框架中提供的標準測試設施。這不是必需的,但通常是設定測試最簡單的方法。
3 應用程式上下文是一個標準的 Spring-WS 應用程式上下文(請參閱 MessageDispatcherServlet),從 spring-ws-servlet.xml 讀取。在這種情況下,應用程式上下文包含 CustomerEndpoint 的 Bean 定義(或者可能使用了 <context:component-scan />)。
4 @Before 方法中,我們使用 createClient 工廠方法建立一個 MockWebServiceClient
5 我們透過呼叫 sendRequest() 並使用靜態匯入的 RequestCreators 提供的 withPayload() RequestCreator(請參閱 使用 RequestCreatorRequestCreators)來發送請求。

我們還透過呼叫 andExpect() 並使用靜態匯入的 ResponseMatchers 提供的 payload() ResponseMatcher(請參閱 使用 ResponseMatcherResponseMatchers)來設定響應期望。

測試的這一部分可能看起來有點令人困惑,但 IDE 的程式碼完成功能提供了很大的幫助。在鍵入 sendRequest( 之後,只要您靜態匯入了 RequestCreators,您的 IDE 就可以為您提供可能的請求建立策略列表。對於 andExpect() 也是如此,只要您靜態匯入了 ResponseMatchers

5.6.2. 使用 RequestCreatorRequestCreators

最初,MockWebServiceClient 需要為端點建立要消費的請求訊息。客戶端為此目的使用 RequestCreator 策略介面

public interface RequestCreator {

  WebServiceMessage createRequest(WebServiceMessageFactory messageFactory)
    throws IOException;

}

您可以編寫此介面的自己的實現,使用訊息工廠建立請求訊息,但您當然不必這樣做。RequestCreators 類提供了一種基於給定有效載荷在 withPayload() 方法中建立 RequestCreator 的方法。您通常靜態匯入 RequestCreators

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

6. 在客戶端使用 Spring Web Services

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

org.springframework.ws.client.core 包提供了使用客戶端訪問 API 的核心功能。它包含模板類,簡化了 Web 服務的使用,就像核心 Spring JdbcTemplate 對 JDBC 所做的那樣。Spring 模板類共同的設計原則是提供輔助方法來執行常見操作,對於更復雜的用法,則委託給使用者實現的 callback 介面。Web 服務模板遵循相同的設計。這些類提供了各種便利方法,用於

  • 傳送和接收 XML 訊息

  • 在傳送前將物件編組為 XML

  • 允許多種傳輸選項

6.1. 使用客戶端 API

本節介紹如何使用客戶端 API。有關如何使用伺服器端 API,請參閱 使用 Spring-WS 建立 Web 服務

6.1.1. WebServiceTemplate

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

URI 和傳輸

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

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>
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 傳輸與 ActiveMQ 連線工廠結合使用

<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>
電子郵件傳輸

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

要使用 MailMessageSender,請將 defaultUriuri 引數設定為 mailto 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>
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>
訊息工廠

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

6.1.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 公開一個無引數建構函式以及 messageFactorymessageSender Bean 屬性,您可以使用它們來構造例項(透過使用 Spring 容器或純 Java 程式碼)。或者,考慮從 Spring-WS 的 WebServiceGatewaySupport 便利基類派生,它公開方便的 Bean 屬性以實現輕鬆配置。(您不必擴充套件此基類。它僅作為便利類提供。)

6.1.3. 傳送和接收 POJO——編組和解組

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

6.1.4. 使用 WebServiceMessageCallback

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

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 頭。
WS-Addressing

除了伺服器端 WS-Addressing支援,Spring Web Services 還支援客戶端的此規範。

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

以下示例將 Action 頭設定為 http://samples/RequestOrder

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

6.1.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.2. 客戶端測試

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

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

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

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

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

6.2.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 中的程式碼完成功能來指導您完成設定模擬伺服器的過程。
另請注意,您可以在單元測試中依賴 Spring Web Services 中提供的標準日誌記錄功能。有時,檢查請求或響應訊息以找出特定測試失敗的原因可能會很有用。有關更多資訊,請參閱訊息日誌記錄和跟蹤

例如,考慮以下 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(請參閱 使用 RequestMatcherRequestMatchers)來定義期望。

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

測試的這一部分可能看起來有點令人困惑,但 IDE 的程式碼完成功能提供了很大的幫助。在您鍵入 expect( 後,只要您靜態匯入了 RequestMatchers,您的 IDE 就可以為您提供可能的請求匹配策略列表。對於 andRespond( 也是如此,只要您靜態匯入了 ResponseCreators

6 我們呼叫 CustomerClient 上的 getCustomerCount(),從而使用 WebServiceTemplate。模板此時已設定為“測試模式”,因此此方法呼叫不會建立實際的(HTTP)連線。我們還根據方法呼叫的結果進行了一些 JUnit 斷言。
7 我們呼叫 MockWebServiceServer 上的 verify(),驗證是否實際收到了預期的訊息。

6.2.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.2.3. 使用 ResponseCreatorResponseCreators

當請求訊息已驗證並符合定義的期望時,MockWebServiceServer 會為 WebServiceTemplate 建立一個響應訊息以供其消費。伺服器為此目的使用 ResponseCreator 策略介面。

public interface ResponseCreator {

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

}

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

ResponseCreators 類提供以下響應:

ResponseCreators 方法 描述

withPayload()

建立一個帶有給定負載的響應訊息。

withError()

在響應連線中建立錯誤。此方法讓您有機會測試錯誤處理。

withException()

從響應連線讀取時丟擲異常。此方法讓您有機會測試異常處理。

withMustUnderstandFault()withClientOrSenderFault()withServerOrReceiverFault()withVersionMismatchFault()

建立一個帶有給定 SOAP 錯誤的響應訊息。此方法讓您有機會測試錯誤處理。

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

7. 使用 Spring-WS 保護您的 Web 服務

本章解釋瞭如何將 WS-Security 方面新增到您的 Web 服務中。我們重點關注 WS-Security 的三個不同領域:

  • 身份驗證:這是一個確定主體是否是其聲稱的身份的過程。在這種情況下,“主體”通常指使用者、裝置或應用程式中可以執行操作的某些其他系統。

  • 數字簽名:訊息的數字簽名是一段基於文件和簽名者私鑰的資訊。它是透過使用雜湊函式和私有簽名函式(使用簽名者的私鑰加密)建立的。

  • 加密和解密:加密是將資料轉換為在沒有適當金鑰的情況下無法讀取的形式的過程。它主要用於將資訊隱藏起來,不讓不應該看到它的人看到。解密是加密的逆過程。它是將加密資料轉換回可讀形式的過程。

這三個領域分別透過使用 XwsSecurityInterceptorWss4jSecurityInterceptor 來實現,我們分別在 XwsSecurityInterceptor使用 Wss4jSecurityInterceptor 中進行描述。

請注意,WS-Security(尤其是加密和簽名)需要大量的記憶體,並且會降低效能。如果效能對您很重要,您可能需要考慮不使用 WS-Security 或使用基於 HTTP 的安全性。

7.1. XwsSecurityInterceptor

XwsSecurityInterceptor 是一個 EndpointInterceptor(請參閱 攔截請求 — EndpointInterceptor 介面),它基於 SUN 的 XML 和 Web 服務安全包 (XWSS)。此 WS-Security 實現是 Java Web Services Developer Pack (Java WSDP) 的一部分。

與其他任何端點攔截器一樣,它在端點對映中定義(請參閱 端點對映)。這意味著您可以選擇性地新增 WS-Security 支援。某些端點對映需要它,而另一些則不需要。

請注意,XWSS 需要 SUN 1.5 JDK 和 SUN SAAJ 參考實現。WSS4J 攔截器沒有這些要求(請參閱 使用 Wss4jSecurityInterceptor)。

XwsSecurityInterceptor 需要一個安全策略檔案才能執行。此 XML 檔案告訴攔截器需要從傳入的 SOAP 訊息中獲取哪些安全方面,以及要新增到傳出訊息中哪些方面。策略檔案的基本格式在以下各節中進行解釋,但您可以在此處找到更深入的教程。您可以使用 policyConfiguration 屬性設定策略,該屬性需要一個 Spring 資源。策略檔案可以包含多個元素 — 例如,要求傳入訊息中包含使用者名稱令牌並簽名所有傳出訊息。它包含一個 SecurityConfiguration 元素(而不是 JAXRPCSecurity 元素)作為其根元素。

此外,安全攔截器需要一個或多個 CallbackHandler 例項才能執行。這些處理程式用於檢索證書、私鑰、驗證使用者憑據等。Spring-WS 為大多數常見的安全問題提供了處理程式 — 例如,針對 Spring Security 身份驗證管理器進行身份驗證,以及基於 X509 證書對傳出訊息進行簽名。以下各節指示針對哪個安全問題使用哪個回撥處理程式。您可以使用 callbackHandlercallbackHandlers 屬性設定回撥處理程式。

以下示例展示瞭如何連線 XwsSecurityInterceptor

<beans>
    <bean id="wsSecurityInterceptor"
        class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
        <property name="policyConfiguration" value="classpath:securityPolicy.xml"/>
        <property name="callbackHandlers">
            <list>
                <ref bean="certificateHandler"/>
                <ref bean="authenticationHandler"/>
            </list>
        </property>
    </bean>
    ...
</beans>

此攔截器透過使用類路徑上的 securityPolicy.xml 檔案進行配置。它使用檔案中稍後定義的兩個回撥處理程式。

7.1.1. 金鑰庫

對於大多數加密操作,您可以使用標準的 java.security.KeyStore 物件。這些操作包括證書驗證、訊息簽名、簽名驗證和加密。它們不包括使用者名稱和時間戳驗證。本節旨在為您提供有關金鑰庫和可用於將金鑰和證書儲存在金鑰庫檔案中的 Java 工具的一些背景知識。此資訊主要與 Spring-WS 無關,而是與 Java 的通用加密功能有關。

java.security.KeyStore 類表示加密金鑰和證書的儲存設施。它可以包含三種不同型別的元素:

  • 私鑰:這些金鑰用於自身份驗證。私鑰附帶相應公鑰的證書鏈。在 WS-Security 領域中,這用於訊息簽名和訊息解密。

  • 對稱金鑰:對稱(或秘密)金鑰也用於訊息加密和解密 — 區別在於雙方(傳送方和接收方)共享相同的秘密金鑰。

  • 受信任證書:這些 X509 證書被稱為“受信任證書”,因為金鑰庫所有者信任證書中的公鑰確實屬於證書所有者。在 WS-Security 中,這些證書用於證書驗證、簽名驗證和加密。

使用 keytool

keytool 程式(一個金鑰和證書管理實用程式)隨您的 Java 虛擬機器提供。您可以使用此工具建立新的金鑰庫,向其中新增新的私鑰和證書等。提供 keytool 命令的完整參考超出了本文件的範圍,但您可以在此處或透過在命令列上使用 keytool -help 命令找到參考。

使用 KeyStoreFactoryBean

為了使用 Spring 配置輕鬆載入金鑰庫,您可以使用 KeyStoreFactoryBean。它具有一個資源位置屬性,您可以將其設定為指向要載入的金鑰庫的路徑。可以提供密碼以檢查金鑰庫資料的完整性。如果未提供密碼,則不執行完整性檢查。以下列表配置了一個 KeyStoreFactoryBean

<bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
    <property name="password" value="password"/>
    <property name="location" value="classpath:org/springframework/ws/soap/security/xwss/test-keystore.jks"/>
</bean>
如果您不指定 location 屬性,則會建立一個新的空金鑰庫,這很可能不是您想要的。
KeyStoreCallbackHandler

要在 XwsSecurityInterceptor 中使用金鑰庫,您需要定義一個 KeyStoreCallbackHandler。此回撥具有三個型別為 keystore 的屬性:(keyStoretrustStoresymmetricStore)。處理程式使用的確切儲存取決於此處理程式要執行的加密操作。對於私鑰操作,使用 keyStore。對於對稱金鑰操作,使用 symmetricStore。為了確定信任關係,使用 trustStore。下表對此進行了說明:

加密操作 使用的金鑰庫

證書驗證

首先 keyStore,然後 trustStore

基於私鑰的解密

keyStore

基於對稱金鑰的解密

symmetricStore

基於公鑰證書的加密

trustStore

基於對稱金鑰的加密

symmetricStore

簽名

keyStore

簽名驗證

trustStore

此外,KeyStoreCallbackHandler 具有一個 privateKeyPassword 屬性,應將其設定為解鎖 keyStore 中包含的私鑰。

如果未設定 symmetricStore,則預設為 keyStore。如果未設定金鑰或信任儲存,則回撥處理程式使用標準 Java 機制載入或建立它。有關此機制如何工作的資訊,請參閱 KeyStoreCallbackHandler 的 JavaDoc。

例如,如果您想使用 KeyStoreCallbackHandler 驗證傳入證書或簽名,您可以使用信任儲存:

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="trustStore" ref="trustStore"/>
    </bean>

    <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:truststore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

如果您想使用它來解密傳入證書或簽名傳出訊息,您可以使用金鑰儲存:

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="keyStore" ref="keyStore"/>
        <property name="privateKeyPassword" value="changeit"/>
    </bean>

    <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:keystore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

以下各節將說明 KeyStoreCallbackHandler 可以用於何處,以及要為特定加密操作設定哪些屬性。

7.1.2. 身份驗證

本章簡介所述,身份驗證是確定主體是否是其聲稱的身份的任務。在 WS-Security 中,身份驗證可以採取兩種形式:使用使用者名稱和密碼令牌(使用明文密碼或密碼摘要)或使用 X509 證書。

明文使用者名稱身份驗證

最簡單的使用者名稱身份驗證形式使用明文密碼。在這種情況下,SOAP 訊息包含一個 UsernameToken 元素,其中包含一個 Username 元素和一個包含明文密碼的 Password 元素。明文身份驗證可以與 HTTP 伺服器提供的基本身份驗證進行比較。

請注意,明文密碼不是很安全。因此,如果您使用它們,您應該始終為您的傳輸層新增額外的安全措施(例如,使用 HTTPS 而不是純 HTTP)。

要要求每條傳入訊息都包含一個帶有明文密碼的 UsernameToken,安全策略檔案應包含一個 RequireUsernameToken 元素,並將 passwordDigestRequired 屬性設定為 false。您可以在此處找到可能的子元素的參考。以下列表顯示瞭如何包含 RequireUsernameToken 元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    ...
    <xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="false"/>
    ...
</xwss:SecurityConfiguration>

如果使用者名稱令牌不存在,XwsSecurityInterceptor 會向傳送方返回一個 SOAP 錯誤。如果它存在,它會向註冊的處理程式發出一個帶有 PlainTextPasswordRequestPasswordValidationCallback。在 Spring-WS 中,有三個類處理此特定回撥。

使用 SimplePasswordValidationCallbackHandler

最簡單的密碼驗證處理程式是 SimplePasswordValidationCallbackHandler。此處理程式根據記憶體中的 Properties 物件驗證密碼,您可以使用 users 屬性指定該物件:

<bean id="passwordValidationHandler"
    class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler">
    <property name="users">
        <props>
            <prop key="Bert">Ernie</prop>
        </props>
    </property>
</bean>

在這種情況下,我們只允許使用者“Bert”使用密碼“Ernie”登入。

使用 SpringPlainTextPasswordValidationCallbackHandler

SpringPlainTextPasswordValidationCallbackHandler 使用 Spring Security 對使用者進行身份驗證。描述 Spring Security 超出了本文件的範圍,但它是一個功能齊全的安全框架。您可以在 Spring Security 參考文件中閱讀更多相關資訊。

SpringPlainTextPasswordValidationCallbackHandler 需要一個 AuthenticationManager 才能執行。它使用此管理器根據它建立的 UsernamePasswordAuthenticationToken 進行身份驗證。如果身份驗證成功,令牌將儲存在 SecurityContextHolder 中。您可以使用 authenticationManager 屬性設定身份驗證管理器:

<beans>
  <bean id="springSecurityHandler"
      class="org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler">
    <property name="authenticationManager" ref="authenticationManager"/>
  </bean>

  <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
      <property name="providers">
          <bean class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
              <property name="userDetailsService" ref="userDetailsService"/>
          </bean>
      </property>
  </bean>

  <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
  ...
</beans>
使用 JaasPlainTextPasswordValidationCallbackHandler

JaasPlainTextPasswordValidationCallbackHandler 基於標準的 Java 身份驗證和授權服務。提供 JAAS 的完整介紹超出了本文件的範圍,但有一個不錯的教程可用。

JaasPlainTextPasswordValidationCallbackHandler 只需要一個 loginContextName 即可執行。它使用此名稱建立一個新的 JAAS LoginContext,並使用 SOAP 訊息中提供的使用者名稱和密碼處理標準的 JAAS NameCallbackPasswordCallback。這意味著此回撥處理程式與在 login() 階段觸發這些回撥的任何 JAAS LoginModule 整合,這是標準行為。

您可以按如下方式連線 JaasPlainTextPasswordValidationCallbackHandler

<bean id="jaasValidationHandler"
    class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasPlainTextPasswordValidationCallbackHandler">
    <property name="loginContextName" value="MyLoginModule" />
</bean>

在這種情況下,回撥處理程式使用名為 MyLoginModuleLoginContext。此模組應在您的 jaas.config 檔案中定義,如前面提到的教程中所述。

摘要使用者名稱身份驗證

當使用密碼摘要時,SOAP 訊息也包含一個 UsernameToken 元素,其中包含一個 Username 元素和一個 Password 元素。區別在於密碼不是以明文形式傳送,而是以摘要形式傳送。接收方將此摘要與他根據使用者已知密碼計算出的摘要進行比較,如果它們相同,則使用者透過身份驗證。此方法可與 HTTP 伺服器提供的摘要身份驗證相媲美。

要要求每條傳入訊息都包含一個帶有密碼摘要的 UsernameToken 元素,安全策略檔案應包含一個 RequireUsernameToken 元素,並將 passwordDigestRequired 屬性設定為 true。此外,nonceRequired 屬性應設定為 true:您可以在此處找到可能的子元素的參考。以下列表顯示瞭如何定義 RequireUsernameToken 元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    ...
    <xwss:RequireUsernameToken passwordDigestRequired="true" nonceRequired="true"/>
    ...
</xwss:SecurityConfiguration>

如果使用者名稱令牌不存在,XwsSecurityInterceptor 會向傳送方返回一個 SOAP 錯誤。如果它存在,它會向註冊的處理程式發出一個帶有 DigestPasswordRequestPasswordValidationCallback。在 Spring-WS 中,有兩個類處理此特定回撥:SimplePasswordValidationCallbackHandlerSpringDigestPasswordValidationCallbackHandler

使用 SimplePasswordValidationCallbackHandler

SimplePasswordValidationCallbackHandler 可以處理明文密碼和密碼摘要。它在使用 SimplePasswordValidationCallbackHandler 中進行了描述。

使用 SpringDigestPasswordValidationCallbackHandler

SpringDigestPasswordValidationCallbackHandler 需要一個 Spring Security UserDetailService 才能執行。它使用此服務檢索令牌中指定使用者的密碼。然後將此詳細資訊物件中包含的密碼摘要與訊息中的摘要進行比較。如果它們相等,則使用者已成功透過身份驗證,並且 UsernamePasswordAuthenticationToken 儲存在 SecurityContextHolder 中。您可以使用 userDetailsService 屬性設定該服務。此外,您可以設定一個 userCache 屬性來快取載入的使用者詳細資訊,如下所示:

<beans>
    <bean class="org.springframework.ws.soap.security.xwss.callback.SpringDigestPasswordValidationCallbackHandler">
        <property name="userDetailsService" ref="userDetailsService"/>
    </bean>

    <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
    ...
</beans>
證書身份驗證

一種更安全的身份驗證方法是使用 X509 證書。在這種情況下,SOAP 訊息包含一個 BinarySecurityToken,其中包含 X509 證書的 Base 64 編碼版本。接收方使用該證書進行身份驗證。訊息中儲存的證書也用於簽名訊息(請參閱 驗證簽名)。

為了確保所有傳入的 SOAP 訊息都攜帶 BinarySecurityToken,安全策略檔案應包含一個 RequireSignature 元素。此元素還可以攜帶其他元素,這些元素在 驗證簽名 中介紹。您可以在此處找到可能的子元素的參考。以下列表顯示瞭如何定義 RequireSignature 元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    ...
    <xwss:RequireSignature requireTimestamp="false">
    ...
</xwss:SecurityConfiguration>

當訊息到達時沒有攜帶證書,XwsSecurityInterceptor 會向傳送方返回一個 SOAP 錯誤。如果存在證書,它會觸發一個 CertificateValidationCallback。Spring-WS 中有三個處理程式為此回撥提供身份驗證目的:

在大多數情況下,證書身份驗證應先於證書驗證,因為您只想針對有效證書進行身份驗證。應忽略無效證書,例如過期或不在您的受信任證書儲存中的證書。

在 Spring-WS 術語中,這意味著 SpringCertificateValidationCallbackHandlerJaasCertificateValidationCallbackHandler 應該在 KeyStoreCallbackHandler 之前。這可以透過在 XwsSecurityInterceptor 的配置中設定 callbackHandlers 屬性的順序來實現:

<bean id="wsSecurityInterceptor"
    class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
    <property name="policyConfiguration" value="classpath:securityPolicy.xml"/>
    <property name="callbackHandlers">
        <list>
            <ref bean="keyStoreHandler"/>
            <ref bean="springSecurityHandler"/>
        </list>
    </property>
</bean>

使用此設定,攔截器首先使用金鑰庫確定訊息中的證書是否有效,然後對其進行身份驗證。

使用 KeyStoreCallbackHandler

KeyStoreCallbackHandler 使用標準的 Java 金鑰庫來驗證證書。此證書驗證過程包括以下步驟:

  1. 處理程式檢查證書是否在私有 keyStore 中。如果是,則它有效。

  2. 如果證書不在私有金鑰庫中,處理程式會檢查當前日期和時間是否在證書中給定的有效期內。如果不是,則證書無效。如果是,則繼續執行最後一步。

  3. 為證書建立認證路徑。這基本上意味著處理程式確定證書是否已由 trustStore 中的任何證書頒發機構頒發。如果認證路徑可以成功構建,則證書有效。否則,證書無效。

要將 KeyStoreCallbackHandler 用於證書驗證目的,您很可能只需要設定 trustStore 屬性:

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="trustStore" ref="trustStore"/>
    </bean>

    <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:truststore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

使用前面示例中所示的設定,要驗證的證書必須在信任儲存本身中,或者信任儲存必須包含頒發該證書的證書頒發機構。

使用 SpringCertificateValidationCallbackHandler

SpringCertificateValidationCallbackHandler 需要一個 Spring Security AuthenticationManager 才能執行。它使用此管理器根據它建立的 X509AuthenticationToken 進行身份驗證。配置的身份驗證管理器應提供一個可以處理此令牌的提供程式(通常是 X509AuthenticationProvider 的例項)。如果身份驗證成功,令牌將儲存在 SecurityContextHolder 中。您可以使用 authenticationManager 屬性設定身份驗證管理器:

<beans>
    <bean id="springSecurityCertificateHandler"
        class="org.springframework.ws.soap.security.xwss.callback.SpringCertificateValidationCallbackHandler">
        <property name="authenticationManager" ref="authenticationManager"/>
    </bean>

    <bean id="authenticationManager"
        class="org.springframework.security.providers.ProviderManager">
        <property name="providers">
            <bean class="org.springframework.ws.soap.security.x509.X509AuthenticationProvider">
                <property name="x509AuthoritiesPopulator">
                    <bean class="org.springframework.ws.soap.security.x509.populator.DaoX509AuthoritiesPopulator">
                        <property name="userDetailsService" ref="userDetailsService"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

  <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
  ...
</beans>

在這種情況下,我們使用自定義使用者詳細資訊服務來獲取基於證書的身份驗證詳細資訊。有關 X509 證書身份驗證的更多資訊,請參閱 Spring Security 參考文件

使用 JaasCertificateValidationCallbackHandler

JaasCertificateValidationCallbackHandler 需要一個 loginContextName 才能執行。它使用此名稱和證書的 X500Principal 建立一個新的 JAAS LoginContext。這意味著此回撥處理程式與處理 X500 主體的任何 JAAS LoginModule 整合。

您可以按如下方式連線 JaasCertificateValidationCallbackHandler

<bean id="jaasValidationHandler"
    class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasCertificateValidationCallbackHandler">
    <property name="loginContextName">MyLoginModule</property>
</bean>

在這種情況下,回撥處理程式使用名為 MyLoginModuleLoginContext。此模組應在您的 jaas.config 檔案中定義,並且應能夠針對 X500 主體進行身份驗證。

7.1.3. 數字簽名

訊息的數字簽名是一段基於文件和簽名者私鑰的資訊。WS-Security 中與簽名相關的兩個主要任務是:驗證簽名和簽名訊息。

驗證簽名

基於證書的身份驗證一樣,已簽名訊息包含一個 BinarySecurityToken,其中包含用於簽名訊息的證書。此外,它還包含一個 SignedInfo 塊,指示訊息的哪些部分已簽名。

為了確保所有傳入的 SOAP 訊息都攜帶 BinarySecurityToken,安全策略檔案應包含一個 RequireSignature 元素。它還可以包含一個 SignatureTarget 元素,該元素指定預期要簽名的目標訊息部分以及各種其他子元素。您還可以定義要使用的私鑰別名、是否使用對稱金鑰而不是私鑰以及許多其他屬性。您可以在此處找到可能的子元素的參考。以下列表配置了一個 RequireSignature 元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:RequireSignature requireTimestamp="false"/>
</xwss:SecurityConfiguration>

如果簽名不存在,XwsSecurityInterceptor 會向傳送方返回一個 SOAP 錯誤。如果簽名存在,它會向註冊的處理程式發出一個 SignatureVerificationKeyCallback。在 Spring-WS 中,有一個類處理此特定回撥:KeyStoreCallbackHandler

使用 KeyStoreCallbackHandler

KeyStoreCallbackHandler 中所述,KeyStoreCallbackHandler 使用 java.security.KeyStore 處理各種加密回撥,包括簽名驗證。對於簽名驗證,處理程式使用 trustStore 屬性:

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="trustStore" ref="trustStore"/>
    </bean>

    <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:org/springframework/ws/soap/security/xwss/test-truststore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>
簽名訊息

簽名訊息時,XwsSecurityInterceptorBinarySecurityToken 新增到訊息中。它還添加了一個 SignedInfo 塊,指示訊息的哪些部分已簽名。

要簽名所有傳出的 SOAP 訊息,安全策略檔案應包含一個 Sign 元素。它還可以包含一個 SignatureTarget 元素,該元素指定預期要簽名的目標訊息部分以及各種其他子元素。您還可以定義要使用的私鑰別名、是否使用對稱金鑰而不是私鑰以及許多其他屬性。您可以在此處找到可能的子元素的參考。以下示例包含一個 Sign 元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
	<xwss:Sign includeTimestamp="false" />
</xwss:SecurityConfiguration>

XwsSecurityInterceptor 會向註冊的處理程式發出一個 SignatureKeyCallback。在 Spring-WS 中,KeyStoreCallbackHandler 類處理此特定回撥。

使用 KeyStoreCallbackHandler

KeyStoreCallbackHandler 中所述,KeyStoreCallbackHandler 使用 java.security.KeyStore 處理各種加密回撥,包括簽名訊息。對於添加簽名,處理程式使用 keyStore 屬性。此外,您必須設定 privateKeyPassword 屬性以解鎖用於簽名的私鑰。以下示例使用 KeyStoreCallbackHandler

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="keyStore" ref="keyStore"/>
        <property name="privateKeyPassword" value="changeit"/>
    </bean>

    <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:keystore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

7.1.4. 解密和加密

加密時,訊息被轉換為只能使用適當金鑰讀取的形式。訊息可以解密以顯示原始的可讀訊息。

解密

要解密傳入的 SOAP 訊息,安全策略檔案應包含一個 RequireEncryption 元素。此元素可以進一步攜帶一個 EncryptionTarget 元素,指示訊息的哪個部分應該被加密,以及一個 SymmetricKey 指示應該使用共享金鑰而不是常規私鑰來解密訊息。您可以在此處閱讀其他元素的描述。以下示例使用 RequireEncryption 元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:RequireEncryption />
</xwss:SecurityConfiguration>

如果傳入訊息未加密,XwsSecurityInterceptor 會向傳送方返回一個 SOAP 錯誤。如果已加密,它會向註冊的處理程式發出一個 DecryptionKeyCallback。在 Spring-WS 中,KeyStoreCallbackHandler 類處理此特定回撥。

使用 KeyStoreCallbackHandler

KeyStoreCallbackHandler 中所述,KeyStoreCallbackHandler 使用 java.security.KeyStore 處理各種加密回撥,包括解密。對於解密,處理程式使用 keyStore 屬性。此外,您必須設定 privateKeyPassword 屬性以解鎖用於解密的私鑰。對於基於對稱金鑰的解密,它使用 symmetricStore。以下示例使用 KeyStoreCallbackHandler

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="keyStore" ref="keyStore"/>
        <property name="privateKeyPassword" value="changeit"/>
    </bean>

    <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:keystore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>
加密

要加密傳出的 SOAP 訊息,安全策略檔案應包含一個 Encrypt 元素。此元素可以進一步攜帶一個 EncryptionTarget 元素,指示訊息的哪個部分應該被加密,以及一個 SymmetricKey 指示應該使用共享金鑰而不是常規公鑰來加密訊息。您可以在此處閱讀其他元素的描述。以下示例使用 Encrypt 元素:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:Encrypt />
</xwss:SecurityConfiguration>

XwsSecurityInterceptor 向註冊的處理程式發出一個 EncryptionKeyCallback 以檢索加密資訊。在 Spring-WS 中,KeyStoreCallbackHandler 類處理此特定回撥。

使用 KeyStoreCallbackHandler

KeyStoreCallbackHandler 中所述,KeyStoreCallbackHandler 使用 java.security.KeyStore 處理各種加密回撥,包括加密。對於基於公鑰的加密,處理程式使用 trustStore 屬性。對於基於對稱金鑰的加密,它使用 symmetricStore。以下示例使用 KeyStoreCallbackHandler

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="trustStore" ref="trustStore"/>
    </bean>

    <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:truststore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>

7.1.5. 安全異常處理

當安全或驗證操作失敗時,XwsSecurityInterceptor 分別丟擲 WsSecuritySecurementExceptionWsSecurityValidationException。這些異常繞過標準異常處理機制,但由攔截器本身處理。

WsSecuritySecurementException 異常由 XwsSecurityInterceptorhandleSecurementException 方法處理。預設情況下,此方法會記錄錯誤並停止訊息的進一步處理。

類似地,WsSecurityValidationException 異常由 XwsSecurityInterceptorhandleValidationException 方法處理。預設情況下,此方法會建立一個 SOAP 1.1 客戶端或 SOAP 1.2 傳送方錯誤並將其作為響應傳送回去。

handleSecurementExceptionhandleValidationException 都是受保護的方法,您可以覆蓋它們以更改其預設行為。

7.2. 使用 Wss4jSecurityInterceptor

Wss4jSecurityInterceptor 是一個 EndpointInterceptor(請參閱 攔截請求 — EndpointInterceptor 介面),它基於 Apache 的 WSS4J

WSS4J 實現以下標準:

  • OASIS Web 服務安全:SOAP 訊息安全 1.0 標準 200401,2004 年 3 月

  • 使用者名稱令牌配置檔案 V1.0

  • X.509 令牌配置檔案 V1.0

此攔截器支援由 AxiomSoapMessageFactorySaajSoapMessageFactory 建立的訊息。

7.2.1. 配置 Wss4jSecurityInterceptor

WSS4J 不使用外部配置檔案。攔截器完全由屬性配置。此攔截器呼叫的驗證和安全操作分別透過 validationActionssecurementActions 屬性指定。操作作為空格分隔的字串傳遞。以下列表顯示了一個示例配置:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="UsernameToken Encrypt"/>
    ...
    <property name="securementActions" value="Encrypt"/>
    ...
</bean>

下表顯示了可用的驗證操作:

驗證操作 描述

UsernameToken

驗證使用者名稱令牌

Timestamp

驗證時間戳

Encrypt

解密訊息

Signature

驗證簽名

NoSecurity

不執行任何操作

下表顯示了可用的安全操作:

安全操作 描述

UsernameToken

新增使用者名稱令牌

UsernameTokenSignature

新增使用者名稱令牌和簽名使用者名稱令牌金鑰

Timestamp

新增時間戳

Encrypt

加密響應

Signature

簽名響應

NoSecurity

不執行任何操作

操作的順序很重要,並由攔截器強制執行。如果其安全操作的執行順序與 validationActions 指定的順序不同,則攔截器會拒絕傳入的 SOAP 訊息。

7.2.2. 處理數字證書

對於需要與金鑰庫或證書處理(簽名、加密和解密操作)互動的加密操作,WSS4J 需要 org.apache.ws.security.components.crypto.Crypto 的例項。

Crypto 例項可以從 WSS4J 的 CryptoFactory 獲取,或者更方便地使用 Spring-WS 的 CryptoFactoryBean 獲取。

CryptoFactoryBean

Spring-WS 提供了一個方便的工廠 Bean CryptoFactoryBean,它透過強型別屬性(首選)或透過 Properties 物件構造和配置 Crypto 例項。

預設情況下,CryptoFactoryBean 返回 org.apache.ws.security.components.crypto.Merlin 的例項。您可以透過設定 cryptoProvider 屬性(或其等效的 org.apache.ws.security.crypto.provider 字串屬性)來更改此設定。

以下示例配置使用 CryptoFactoryBean

<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
    <property name="keyStorePassword" value="mypassword"/>
    <property name="keyStoreLocation" value="file:/path_to_keystore/keystore.jks"/>
</bean>

7.2.3. 身份驗證

本節討論如何使用 Wss4jSecurityInterceptor 進行身份驗證。

驗證使用者名稱令牌

Spring-WS 提供了一組回撥處理程式以與 Spring Security 整合。此外,還提供了一個簡單的回撥處理程式 SimplePasswordValidationCallbackHandler,用於透過記憶體中的 Properties 物件配置使用者和密碼。

回撥處理程式透過 Wss4jSecurityInterceptor 屬性的 validationCallbackHandler 進行配置。

使用 SimplePasswordValidationCallbackHandler

SimplePasswordValidationCallbackHandler 根據記憶體中的 Properties 物件驗證明文和摘要使用者名稱令牌。您可以按如下方式配置它:

<bean id="callbackHandler"
    class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler">
    <property name="users">
        <props>
            <prop key="Bert">Ernie</prop>
        </props>
    </property>
</bean>
使用 SpringSecurityPasswordValidationCallbackHandler

SpringSecurityPasswordValidationCallbackHandler 透過使用 Spring Security UserDetailService 來驗證明文和摘要密碼。它使用此服務來檢索令牌中指定使用者的密碼(或密碼的摘要)。然後將此詳細資訊物件中包含的密碼(或密碼的摘要)與訊息中的摘要進行比較。如果它們相等,則使用者已成功透過身份驗證,並且 UsernamePasswordAuthenticationToken 儲存在 SecurityContextHolder 中。您可以使用 userDetailsService 設定服務。此外,您可以設定一個 userCache 屬性來快取載入的使用者詳細資訊,如下所示:

<beans>
    <bean class="org.springframework.ws.soap.security.wss4j.callback.SpringDigestPasswordValidationCallbackHandler">
        <property name="userDetailsService" ref="userDetailsService"/>
    </bean>

    <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
    ...
</beans>
新增使用者名稱令牌

向傳出訊息新增使用者名稱令牌就像將 UsernameToken 新增到 Wss4jSecurityInterceptorsecurementActions 屬性並指定 securementUsernamesecurementPassword 一樣簡單。

密碼型別可以透過設定 securementPasswordType 屬性來設定。可能的值是 PasswordText 用於明文密碼,或 PasswordDigest 用於摘要密碼,這是預設值。

以下示例生成一個帶有摘要密碼的使用者名稱令牌:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="UsernameToken"/>
    <property name="securementUsername" value="Ernie"/>
    <property name="securementPassword" value="Bert"/>
</bean>

如果選擇明文密碼型別,可以透過設定 securementUsernameTokenElements 屬性來指示攔截器新增 NonceCreated 元素。該值必須是包含所需元素名稱(區分大小寫)的空格分隔列表。

以下示例生成一個帶有明文密碼、NonceCreated 元素的使用者名稱令牌:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="UsernameToken"/>
    <property name="securementUsername" value="Ernie"/>
    <property name="securementPassword" value="Bert"/>
    <property name="securementPasswordType" value="PasswordText"/>
    <property name="securementUsernameTokenElements" value="Nonce Created"/>
</bean>
證書身份驗證

由於證書身份驗證類似於數字簽名,WSS4J 將其作為簽名驗證和安全的一部分進行處理。具體來說,securementSignatureKeyIdentifier 屬性必須設定為 DirectReference,以指示 WSS4J 生成一個包含 X509 證書的 BinarySecurityToken 元素並將其包含在傳出訊息中。證書的名稱和密碼分別透過 securementUsernamesecurementPassword 屬性傳遞,如以下示例所示:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Signature"/>
    <property name="securementSignatureKeyIdentifier" value="DirectReference"/>
    <property name="securementUsername" value="mycert"/>
    <property name="securementPassword" value="certpass"/>
    <property name="securementSignatureCrypto">
      <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
        <property name="keyStorePassword" value="123456"/>
        <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
      </bean>
    </property>
</bean>

對於證書驗證,適用常規簽名驗證:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="validationSignatureCrypto">
      <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
        <property name="keyStorePassword" value="123456"/>
        <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
      </bean>
    </property>
</bean>

在驗證結束時,攔截器透過委託給預設的 WSS4J 實現自動驗證證書的有效性。如果需要,您可以透過重新定義 verifyCertificateTrust 方法來更改此行為。

有關更多詳細資訊,請參閱 數字簽名

7.2.4. 安全時間戳

本節描述了 Wss4jSecurityInterceptor 中可用的各種時間戳選項。

驗證時間戳

要驗證時間戳,請將 Timestamp 新增到 validationActions 屬性中。您可以透過將 timestampStrict 設定為 true 並透過設定 timeToLive 屬性來指定伺服器端生存時間(預設為:300)來覆蓋 SOAP 訊息發起者指定的時間戳語義。無論 timeToLive 的值如何,攔截器始終拒絕已經過期的時間戳。

在以下示例中,攔截器將時間戳有效期視窗限制為 10 秒,拒絕該視窗之外的任何有效時間戳令牌:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Timestamp"/>
    <property name="timestampStrict" value="true"/>
    <property name="timeToLive" value="10"/>
</bean>
新增時間戳

Timestamp 新增到 securementActions 屬性會在傳出訊息中生成時間戳頭。timestampPrecisionInMilliseconds 屬性指定生成的時間戳的精度是否以毫秒為單位。預設值為 true。以下列表添加了一個時間戳:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Timestamp"/>
    <property name="timestampPrecisionInMilliseconds" value="true"/>
</bean>

7.2.5. 數字簽名

本節描述了 Wss4jSecurityInterceptor 中可用的各種簽名選項。

驗證簽名

要指示 Wss4jSecurityInterceptorvalidationActions 必須包含 Signature 操作。此外,validationSignatureCrypto 屬性必須指向包含發起者公共證書的金鑰庫:

<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="validationSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
</bean>
簽名訊息

透過將 Signature 操作新增到 securementActions 來啟用傳出訊息的簽名。要使用的私鑰的別名和密碼分別由 securementUsernamesecurementPassword 屬性指定。securementSignatureCrypto 必須指向包含私鑰的金鑰庫:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Signature"/>
    <property name="securementUsername" value="mykey"/>
    <property name="securementPassword" value="123456"/>
    <property name="securementSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
</bean>

此外,您可以透過設定 securementSignatureAlgorithm 屬性來定義簽名演算法。

您可以透過設定 securementSignatureKeyIdentifier 屬性來自定義要使用的金鑰識別符號型別。只有 IssuerSerialDirectReference 對簽名有效。

securementSignatureParts 屬性控制訊息的哪些部分被簽名。此屬性的值是一個由分號分隔的元素名稱列表,用於標識要簽名的元素。簽名部分的一般形式是 {}{namespace}Element。請注意,第一個空括號僅用於加密部分。預設行為是簽名 SOAP 正文。

以下示例顯示瞭如何在 Spring Web Services echo 示例中籤名 echoResponse 元素:

<property name="securementSignatureParts"
    value="{}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>

要指定沒有名稱空間的元素,請使用字串 Null(區分大小寫)作為名稱空間名稱。

如果請求中沒有其他元素的本地名稱為 Body,則 SOAP 名稱空間識別符號可以為空 ({})。

簽名確認

透過將 enableSignatureConfirmation 設定為 true 來啟用簽名確認。請注意,簽名確認操作跨越請求和響應。這意味著即使沒有相應的安全操作,也必須將 secureResponsevalidateRequest 設定為 true(這是預設值)。以下示例將 enableSignatureConfirmation 屬性設定為 true

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="enableSignatureConfirmation" value="true"/>
    <property name="validationSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="file:/keystore.jks"/>
        </bean>
    </property>
</bean>

7.2.6. 解密和加密

本節描述了 Wss4jSecurityInterceptor 中可用的各種解密和加密選項。

解密

傳入 SOAP 訊息的解密要求將 Encrypt 操作新增到 validationActions 屬性中。(這是因為 WSS4J 只需要一個用於加密金鑰的 Crypto,而嵌入式金鑰名稱驗證則委託給回撥處理程式。)

要解密具有嵌入式加密對稱金鑰(xenc:EncryptedKey 元素)的訊息,validationDecryptionCrypto 需要指向包含解密私鑰的金鑰庫。此外,必須向 validationCallbackHandler 注入一個 org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler,該處理程式指定金鑰的密碼:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Encrypt"/>
    <property name="validationDecryptionCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
    <property name="validationCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
            <property name="privateKeyPassword" value="mykeypass"/>
        </bean>
    </property>
</bean>

為了支援解密帶有嵌入式金鑰名稱(ds:KeyName 元素)的訊息,您可以配置一個 KeyStoreCallbackHandler,它指向帶有對稱金鑰的金鑰庫。symmetricKeyPassword 屬性指示金鑰的密碼,金鑰名稱是由 ds:KeyName 元素指定的名稱:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Encrypt"/>
    <property name="validationCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
            <property name="keyStore">
                <bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
                    <property name="location" value="classpath:keystore.jks"/>
                    <property name="type" value="JCEKS"/>
                    <property name="password" value="123456"/>
                </bean>
            </property>
            <property name="symmetricKeyPassword" value="mykeypass"/>
        </bean>
    </property>
</bean>
加密

Encrypt 新增到 securementActions 可啟用傳出訊息的加密。您可以透過設定 securementEncryptionUser 屬性來設定用於加密的證書別名。包含證書的金鑰庫透過 securementEncryptionCrypto 屬性訪問。由於加密依賴於公共證書,因此無需傳遞密碼。以下示例使用 securementEncryptionCrypto 屬性:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Encrypt"/>
    <property name="securementEncryptionUser" value="mycert"/>
    <property name="securementEncryptionCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="file:/keystore.jks"/>
        </bean>
    </property>
</bean>

您可以透過多種方式自定義加密:要使用的金鑰識別符號型別由 securementEncryptionKeyIdentifier 屬性定義。可能的值是 IssuerSerialX509KeyIdentifierDirectReferenceThumbprintSKIKeyIdentifierEmbeddedKeyName

如果選擇 EmbeddedKeyName 型別,您需要指定用於加密的金鑰。金鑰的別名在 securementEncryptionUser 屬性中設定,與其他金鑰識別符號型別相同。但是,WSS4J 需要一個回撥處理程式來獲取金鑰。因此,您必須向 securementCallbackHandler 提供一個指向適當金鑰庫的 KeyStoreCallbackHandler。預設情況下,結果 WS-Security 頭中的 ds:KeyName 元素採用 securementEncryptionUser 屬性的值。要指示不同的名稱,您可以使用所需值設定 securementEncryptionEmbeddedKeyName。在下一個示例中,傳出訊息使用別名為 secretKey 的金鑰進行加密,而 myKey 出現在 ds:KeyName 元素中:

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Encrypt"/>
    <property name="securementEncryptionKeyIdentifier" value="EmbeddedKeyName"/>
    <property name="securementEncryptionUser" value="secretKey"/>
    <property name="securementEncryptionEmbeddedKeyName" value="myKey"/>
    <property name="securementCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
            <property name="symmetricKeyPassword" value="keypass"/>
            <property name="keyStore">
                <bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
                    <property name="location" value="file:/keystore.jks"/>
                    <property name="type" value="jceks"/>
                    <property name="password" value="123456"/>
                </bean>
            </property>
        </bean>
    </property>
</bean>

securementEncryptionKeyTransportAlgorithm 屬性定義用於加密生成的對稱金鑰的演算法。支援的值是 http://www.w3.org/2001/04/xmlenc#rsa-1_5(預設值)和 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p

最後,您可以透過設定 securementEncryptionSymAlgorithm 屬性來設定要使用的對稱加密演算法。支援的值是 http://www.w3.org/2001/04/xmlenc#aes128-cbc(預設)、http://www.w3.org/2001/04/xmlenc#tripledes-cbchttp://www.w3.org/2001/04/xmlenc#aes256-cbchttp://www.w3.org/2001/04/xmlenc#aes192-cbc

最後,securementEncryptionParts 屬性定義訊息的哪些部分被加密。此屬性的值是一個由分號分隔的元素名稱列表,用於標識要加密的元素。每個元素名稱前面可以有一個加密模式說明符和一個名稱空間識別符號,每個都包含在一對花括號中。加密模式說明符可以是 {Content}{Element}。請參閱 W3C XML 加密規範瞭解元素加密和內容加密之間的差異。以下示例標識了 echo 示例中的 echoResponse

<property name="securementEncryptionParts"
    value="{Content}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>

請注意,元素名稱、名稱空間識別符號和加密修飾符是區分大小寫的。您可以省略加密修飾符和名稱空間識別符號。如果省略,加密模式預設為 Content,名稱空間設定為 SOAP 名稱空間。

要指定沒有名稱空間的元素,請使用值 Null(區分大小寫)作為名稱空間名稱。如果未指定列表,則處理程式預設以 Content 模式加密 SOAP 正文。

7.2.7. 安全異常處理

Wss4jSecurityInterceptor 的異常處理與 XwsSecurityInterceptor 的異常處理相同。有關更多資訊,請參閱 安全異常處理

三、其他資源

除了本參考文件之外,還有許多其他資源可以幫助您學習如何使用 Spring Web Services。本節列出了這些額外的第三方資源。

參考書目

  • [waldo-94] Jim Waldo, Ann Wollrath, and Sam Kendall. 分散式計算札記. Springer Verlag. 1994

  • [alpine] Steve Loughran & Edmund Smith. 重新思考 Java SOAP 棧. 2005 年 5 月 17 日。© 2005 IEEE 電話實驗室公司。

  • [effective-enterprise-java] Ted Neward. Scott Meyers. 高效企業 Java. Addison-Wesley. 2004

  • [effective-xml] Elliotte Rusty Harold. Scott Meyers. 高效 XML. Addison-Wesley. 2004

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