© 2005-2020 原始作者。
允許為自用和分發目的複製本文件,前提是您不為此類複製收取任何費用,並且無論以印刷或電子方式分發,每份副本都包含此版權宣告。 |
前言
在當前面向服務架構(SOA)的時代,越來越多的人使用 Web Services 連線以前互不關聯的系統。最初,Web Services 被認為是執行遠端過程呼叫(RPC)的另一種方式。然而隨著時間的推移,人們發現 RPC 和 Web Services 之間存在很大的區別。特別是當跨平臺互操作性很重要時,傳送包含處理請求所需所有資料的封裝 XML 文件通常是更好的選擇。從概念上講,基於 XML 的 Web Services 與訊息佇列相比,比遠端解決方案更具可比性。總的來說,XML 應該被視為資料的平臺無關表示形式,是 SOA 的通用語言。在開發或使用 Web Services 時,重點應放在 XML 上,而不是 Java 上。
Spring Web Services 專注於建立這些文件驅動的 Web Services。Spring Web Services 促進了契約優先的 SOAP 服務開發,透過使用多種處理 XML 有效負載的方式,允許建立靈活的 Web Services。Spring-WS 提供了一個強大的訊息分發框架,一個與現有應用程式安全解決方案整合的 WS-Security 解決方案,以及遵循熟悉的 Spring 模板模式的客戶端 API。
I. 介紹
1. 什麼是 Spring Web Services?
1.1. 介紹
Spring Web Services (Spring-WS) 是 Spring 社群的一個產品,專注於建立文件驅動的 Web Services。Spring Web Services 旨在促進契約優先的 SOAP 服務開發,透過使用多種方式處理 XML 有效負載來建立靈活的 Web Services。該產品基於 Spring 本身,這意味著你可以將 Spring 概念(如依賴注入)作為 Web Service 的一個組成部分來使用。
人們使用 Spring-WS 的原因有很多,但大多數人在發現其他 SOAP 棧在遵循 Web Service 最佳實踐方面有所欠缺後轉向它。Spring-WS 使最佳實踐變得容易實現。這包括 WS-I 基本配置檔案、契約優先開發以及契約與實現之間的鬆散耦合等實踐。Spring Web Services 的其他關鍵特性包括
1.1.3. 靈活的 XML 編組
Spring Web Services 構建在 Spring Framework 的物件/XML 對映模組之上,該模組支援 JAXB 1 和 2、Castor、XMLBeans、JiBX 和 XStream。
1.1.4. 重用你的 Spring 經驗
Spring-WS 使用 Spring 應用程式上下文進行所有配置,這應該能幫助 Spring 開發人員快速上手。此外,Spring-WS 的架構與 Spring-MVC 的架構相似。
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 Service 開發人員。 -
核心模組 (
spring-ws-core.jar
) 是 Spring Web Services 功能的核心部分。它提供了核心的WebServiceMessage
和SoapMessage
介面,伺服器端框架(具有強大的訊息分發能力),用於實現 Web Service 端點的各種支援類,以及客戶端WebServiceTemplate
。 -
支援模組 (
spring-ws-support.jar
) 包含額外的傳輸(JMS、Email 等)。 -
安全包 (
spring-ws-security.jar
) 提供了一個 WS-Security 實現,該實現與核心 Web Service 包整合。它允許你對 SOAP 訊息進行簽名、解密、加密,並新增主體令牌。此外,它允許你使用現有的 Spring Security 安全實現進行身份驗證和授權。
下圖顯示了 Spring-WS 模組之間的依賴關係。箭頭表示依賴(即 Spring-WS Core 依賴於 Spring-XML 和 Spring 3 及更高版本中的 OXM 模組)。

1.3. 支援的標準
Spring Web Services 支援以下標準
-
SOAP 1.1 和 1.2
-
WSDL 1.1 和 2.0 (僅支援基於 XSD 生成 WSDL 1.1)
-
WS-I 基本配置檔案 1.0, 1.1, 1.2, 和 2.0
-
WS-Addressing 1.0 和 2004 年 8 月草案
-
SOAP Message Security 1.1, Username Token Profile 1.1, X.509 Certificate Token Profile 1.1, SAML Token Profile 1.1, Kerberos Token Profile 1.1, Basic Security Profile 1.1
2. 為什麼採用契約優先?
在建立 Web Services 時,有兩種開發風格:契約後置(contract-last)和契約優先(contract-first)。使用契約後置方法時,你從 Java 程式碼開始,然後從程式碼生成 Web Service 契約(WSDL — 參見側邊欄)。使用契約優先方法時,你從 WSDL 契約開始,然後使用 Java 實現該契約。
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 Service 最重要的目標之一是實現互操作性:支援 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;
}
毫無疑問,這個 map 的內容可以轉換為某種形式的 XML,但由於 XML 中沒有描述 map 的標準方法,因此它將是專有的。此外,即使它可以轉換為 XML,許多平臺也沒有類似於 TreeMap
的資料結構。因此,當 .NET 客戶端訪問你的 Web Service 時,它可能會得到一個語義不同的 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.Date
或 java.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 Basic Profile)。
這些只是處理 O/X 對映時遇到的一些問題。在編寫 Web Services 時,重要的是要尊重這些問題。最好的方法是完全專注於 XML,同時使用 Java 作為實現語言。這就是契約優先的全部意義所在。
2.2. 契約優先 vs 契約後置
除了上一節提到的物件/XML 對映問題外,還有其他原因偏好契約優先的開發風格。
2.2.1. 脆弱性
如前所述,契約後置開發風格導致 Web Service 契約(WSDL 和 XSD)從你的 Java 契約(通常是一個介面)生成。如果採用這種方法,你無法保證契約隨時間保持不變。每次更改 Java 契約並重新部署時,Web Service 契約可能會隨之改變。
此外,並非所有 SOAP 棧都會從 Java 契約生成相同的 Web Service 契約。這意味著出於任何原因更改當前 SOAP 棧可能會改變你的 Web Service 契約。
當 Web Service 契約發生變化時,需要告知使用該契約的使用者獲取新契約,並可能需要更改他們的程式碼以適應契約的任何變化。
一個有用的契約必須儘可能長時間保持不變。如果契約發生變化,你必須聯絡所有使用你的服務的使用者,並告知他們獲取新版本的契約。
2.2.2. 效能
當 Java 物件自動轉換為 XML 時,無法確定透過網路傳送了什麼。一個物件可能引用另一個物件,後者又引用另一個物件,依此類推。最終,虛擬機器堆中的一半物件可能被轉換為 XML,從而導致響應時間變慢。
使用契約優先時,你明確地描述了將什麼 XML 傳送到哪裡,從而確保它完全是你想要的。
3. 編寫契約優先的 Web Services
本教程將向你展示如何編寫契約優先的 Web Services — 也就是說,如何先從 XML Schema 或 WSDL 契約開始,然後編寫 Java 程式碼。Spring-WS 專注於這種開發風格,本教程應該能幫助你入門。請注意,本教程的第一部分幾乎不包含 Spring-WS 特定的資訊。它主要關於 XML、XSD 和 WSDL。第二部分著重於使用 Spring-WS 實現這個契約。
進行契約優先 Web Service 開發時,最重要的事情是以 XML 的方式思考。這意味著 Java 語言的概念相對次要。透過網路傳送的是 XML,你應該專注於此。使用 Java 實現 Web Service 只是一個實現細節。
在本教程中,我們將定義一個由人力資源部門建立的 Web Service。客戶端可以向此服務傳送假期請求表來預訂假期。
3.1. 訊息
在本節中,我們將重點介紹傳送到 Web Service 和從 Web Service 傳送的實際 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 的此類契約
DTD 對名稱空間的支援有限,因此不適用於 Web Services。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 Service 應該能夠接受所有這些元素作為資料。這是不希望的:我們只想接受一個 <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
s 更改為 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>
我們將訊息新增到一個埠型別(port type)作為操作(operation)
<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 | 我們使用 document/literal 風格。 |
7 | 文字 http://schemas.xmlsoap.org/soap/http 表示 HTTP 傳輸。 |
8 | soapAction 屬性表示將隨每個請求傳送的 SOAPAction HTTP 頭部。 |
9 | https://:8080/holidayService/ 地址是 Web Service 可以被呼叫的 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,例如 EndPoints
和 WebServiceMessageReceivers
,並用於建立一個新的 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
檔案內容。)
一旦建立了專案結構,你就可以將上一節中的 schema 和 WSDL 放入 'WEB-INF/'
資料夾中。
3.5. 實現 Endpoint
在 Spring-WS 中,你實現 endpoint 來處理入站 XML 訊息。endpoint 通常透過使用 @Endpoint
註解來註解一個類來建立。在這個 endpoint 類中,你可以建立一個或多個方法來處理入站請求。方法的簽名可以非常靈活。你可以包含幾乎任何與入站 XML 訊息相關的引數型別,我們將在本章後面進行解釋。
3.5.1. 處理 XML 訊息
以下列表顯示了定義我們 holiday endpoint 的類
package com.mycompany.hr.ws;
@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 表示式。有四個表示式:用於提取 <StartDate> 文字值的 //hr:StartDate ,用於提取結束日期的 //hr:EndDate ,以及兩個用於提取員工姓名的表示式。 |
3 | @PayloadRoot 註解告訴 Spring-WS,handleHolidayRequest 方法適合處理 XML 訊息。此方法可以處理的訊息型別由註解值指定。在本例中,它可以處理具有 HolidayRequest local part 和名稱空間 http://mycompany.com/hr/schemas 的 XML 元素。有關將訊息對映到 endpoint 的更多資訊,請參見下一節。 |
4 | handleHolidayRequest(..) 方法是主要的處理方法,它接收入站 XML 訊息中的 <HolidayRequest/> 元素。@RequestPayload 註解表示 holidayRequest 引數應對映到請求訊息的 payload。我們使用 XPath 表示式從 XML 訊息中提取字串值,並使用 SimpleDateFormat (parseData 方法)將這些值轉換為 Date 物件。有了這些值,我們呼叫業務服務上的一個方法。通常,這將啟動一個數據庫事務並更改資料庫中的一些記錄。最後,我們定義了一個 void 返回型別,這表明我們不想向 Spring-WS 傳送響應訊息。如果需要響應訊息,我們可以返回一個 JDOM Element 來表示響應訊息的 payload。 |
使用 JDOM 只是處理 XML 的一種選擇。其他選項包括 DOM、dom4j、XOM、SAX 和 StAX,以及編組技術(如 JAXB、Castor、XMLBeans、JiBX 和 XStream),如 下一章 所述。我們選擇 JDOM 是因為它允許我們訪問原始 XML,並且它是基於類的(不像 W3C DOM 和 dom4j 那樣基於介面和工廠方法),這使得程式碼更簡潔。我們使用 XPath 是因為它比編組技術更不容易出錯。只要能找到日期和姓名,就不需要嚴格遵守 schema。
由於我們使用 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 配置檔案中使用元件掃描配置這些類的方式。我們還透過 <sws:annotation-driven>
元素指示 Spring-WS 使用註解驅動的 endpoint。
<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. 將訊息路由到 Endpoint
作為編寫 endpoint 的一部分,我們還使用了 @PayloadRoot
註解來指示 handleHolidayRequest
方法可以處理哪種訊息。在 Spring-WS 中,這個過程是由 EndpointMapping
負責的。在這裡,我們透過使用 PayloadRootAnnotationMethodEndpointMapping
根據訊息內容來路由訊息。以下列表顯示了我們之前使用的註解
@PayloadRoot(namespace = "http://mycompany.com/hr/schemas", localPart = "HolidayRequest")
上述示例中顯示的註解基本意味著,每當接收到具有名稱空間 http://mycompany.com/hr/schemas
和 local name HolidayRequest
的 XML 訊息時,它將被路由到 handleHolidayRequest
方法。透過在我們的配置中使用 <sws:annotation-driven>
元素,我們啟用對 @PayloadRoot
註解的檢測。在一個 endpoint 中擁有多個相關的處理方法是可能且很常見的,每個方法處理不同的 XML 訊息。
還有其他方法可以將 endpoint 對映到 XML 訊息,這將在 下一章 中描述。
3.5.3. 提供 Service 和 Stub 實現
現在我們有了 endpoint,我們需要 HumanResourceService
及其實現供 HolidayEndpoint
使用。以下列表顯示了 HumanResourceService
介面
package com.mycompany.hr.service;
public interface HumanResourceService {
void bookHoliday(Date startDate, Date endDate, String name);
}
出於教程目的,我們使用 HumanResourceService
的一個簡單 stub 實現
package com.mycompany.hr.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。如 Service Contract 中所述,我們不需要自己編寫 WSDL。Spring-WS 可以根據一些約定生成一個 WSDL。以下是我們定義生成的方式
<sws:dynamic-wsdl id="holiday" (1)
portTypeName="HumanResource" (3)
locationUri="/holidayService/" (4)
targetNamespace="http://mycompany.com/hr/definitions"> (5)
<sws:xsd location="/WEB-INF/hr.xsd"/> (2)
</sws:dynamic-wsdl>
1 | id 決定了可以檢索 WSDL 的 URL。在本例中,id 是 holiday ,這意味著可以在 servlet 上下文中以 holiday.wsdl 的形式檢索 WSDL。完整的 URL 是 https://:8080/holidayService/holiday.wsdl 。 |
2 | 接下來,我們將 WSDL 的 port type 設定為 HumanResource 。 |
3 | 我們設定了服務可達到的位置:/holidayService/ 。我們使用相對 URI,並指示框架將其動態轉換為絕對 URI。因此,如果服務部署到不同的上下文,我們無需手動更改 URI。更多資訊,請參見 “自動 WSDL 暴露” 一節。為了使位置轉換工作,我們需要在 web.xml 中的 spring-ws servlet 中新增一個 init 引數(如下一個列表所示)。 |
4 | 我們定義了 WSDL 定義本身的目標名稱空間。設定此屬性不是必需的。如果未設定,WSDL 將具有與 XSD schema 相同的名稱空間。 |
5 | xsd 元素引用了我們在 Data Contract 中定義的人力資源 schema。我們將 schema 放在應用程式的 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. 參考
4. 共享元件
本章探討了在 Spring-WS 客戶端和伺服器端開發之間共享的元件。這些介面和類代表了 Spring-WS 的構建塊,因此你需要了解它們的作用,即使你不直接使用它們。
4.1. Web Service 訊息
本節描述了 Spring-WS 使用的訊息和訊息工廠。
4.1.1. WebServiceMessage
Spring Web Services 的核心介面之一是 WebServiceMessage
。此介面代表與協議無關的 XML 訊息。該介面包含提供對訊息 payload 訪問的方法,形式為 javax.xml.transform.Source
或 javax.xml.transform.Result
。Source
和 Result
是標記介面,代表對 XML 輸入和輸出的抽象。具體實現包裝了各種 XML 表示,如下表所示
Source 或 Result 實現 | 包裝的 XML 表示 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
除了從 payload 讀取和向 payload 寫入之外,Web 服務訊息還可以將自身寫入輸出流。
4.1.2. SoapMessage
SoapMessage
是 WebServiceMessage
的一個子類。它包含 SOAP 特定的方法,例如獲取 SOAP Headers、SOAP Faults 等等。通常,你的程式碼不應該依賴於 SoapMessage
,因為可以使用 WebServiceMessage
中的 getPayloadSource()
和 getPayloadResult()
來獲取 SOAP Body 的內容(訊息的 payload)。只有在需要執行 SOAP 特定操作(例如新增 header、獲取附件等)時,才需要將 WebServiceMessage
轉換為 SoapMessage
。
4.1.3. 訊息工廠
具體的訊息實現由 WebServiceMessageFactory
建立。此工廠可以建立一個空訊息或從輸入流讀取訊息。WebServiceMessageFactory
有兩個具體的實現。一個基於 SAAJ,即 Java 的 SOAP with Attachments API。另一個基於 Axis 2 的 AXIOM (AXis Object Model)。
SaajSoapMessageFactory
SaajSoapMessageFactory
使用 Java 的 SOAP with Attachments 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 實現中有一個已知的 bug:它實現了所有的 1.2 介面,但在呼叫時會丟擲 |
此外,Java SE 6 包括 SAAJ 1.3。你可以如下配置 SaajSoapMessageFactory
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
SAAJ 基於 DOM,即 Document Object Model。這意味著所有 SOAP 訊息都儲存在記憶體中。對於較大的 SOAP 訊息,這可能不夠高效。在這種情況下,AxiomSoapMessageFactory 可能更適用。 |
AxiomSoapMessageFactory
AxiomSoapMessageFactory
使用 AXis 2 Object Model (AXIOM) 來建立 SoapMessage
實現。AXIOM 基於 StAX,即 Streaming API for XML。StAX 提供了一種基於拉的機制來讀取 XML 訊息,這對於較大的訊息更有效率。
為了提高 AxiomSoapMessageFactory
的讀取效能,你可以將 payloadCaching
屬性設定為 false(預設值為 true)。這樣做會導致 SOAP body 的內容直接從 socket 流中讀取。啟用此設定後,payload 只能讀取一次。這意味著你必須確保訊息的任何預處理(日誌記錄或其他工作)不會消耗它。
你可以如下使用 AxiomSoapMessageFactory
<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory">
<property name="payloadCaching" value="true"/>
</bean>
除了 payload 快取之外,AXIOM 還支援完整的流式訊息,如 StreamingWebServiceMessage
中定義的那樣。這意味著你可以直接在響應訊息上設定 payload,而不是將其寫入 DOM 樹或緩衝區。
當 handler 方法返回一個 JAXB2 支援的物件時,將使用 AXIOM 的完整流處理。它會自動將此已編組的物件設定到響應訊息中,並在響應輸出時將其寫入到傳出的 socket 流中。
有關完整流處理的更多資訊,請參閱 StreamingWebServiceMessage
和 StreamingPayload
的類級別 Javadoc。
SOAP 1.1 或 1.2
SaajSoapMessageFactory
和 AxiomSoapMessageFactory
都有一個 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>
在上述示例中,我們定義了一個只接受 SOAP 1.2 訊息的 SaajSoapMessageFactory
。
儘管 SOAP 的兩個版本在格式上非常相似,但 1.2 版本與 1.1 不向後相容,因為它使用了不同的 XML 名稱空間。SOAP 1.1 和 1.2 之間的其他主要區別包括 fault 的結構不同,以及 關於 SOAP 版本號(或通常的 WS-* 規範版本號)需要注意的重要一點是,規範的最新版本通常不是最流行的版本。對於 SOAP 來說,這意味著(目前)最好使用的版本是 1.1。版本 1.2 未來可能會變得更受歡迎,但目前 1.1 是最安全的選擇。 |
4.1.4. MessageContext
通常,訊息成對出現:一個請求和一個響應。請求在客戶端建立,透過某種傳輸傳送到伺服器端,伺服器端生成響應。此響應傳送回客戶端,並在客戶端讀取。在 Spring Web Services 中,這種對話包含在 MessageContext
中,它具有獲取請求和響應訊息的屬性。在客戶端,訊息上下文由 WebServiceTemplate
建立。在伺服器端,訊息上下文從特定於傳輸的輸入流中讀取。例如,在 HTTP 中,它從 HttpServletRequest
中讀取,響應寫回 HttpServletResponse
。
SOAP 協議的一個關鍵特性是它試圖與傳輸無關。這就是為什麼 Spring-WS 不支援透過 HTTP 請求 URL 而是透過訊息內容將訊息對映到 endpoint 的原因。
然而,有時需要在客戶端或伺服器端訪問底層傳輸。為此,Spring Web Services 提供了 TransportContext
。傳輸上下文允許訪問底層的 WebServiceConnection
,在伺服器端通常是 HttpServletConnection
,在客戶端通常是 HttpUrlConnection
或 CommonsHttpConnection
。例如,你可以在伺服器端 endpoint 或 interceptor 中獲取當前請求的 IP 地址
然而,有時需要在客戶端或伺服器端訪問底層的傳輸。為此,Spring Web Services 提供了 TransportContext
。傳輸上下文允許訪問底層的 WebServiceConnection
,這通常在伺服器端是 HttpServletConnection
,在客戶端是 HttpUrlConnection
或 CommonsHttpConnection
。例如,您可以在伺服器端端點或攔截器中獲取當前請求的 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 表示式對於輸入文件中意料之外但可能不重要的更改通常更具健壯性。
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 中常用的模板模式(JdbcTemplate
、JmsTemplate
等)。以下列表顯示了一個示例
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
logger 級別設定為 DEBUG
或 TRACE
。在 DEBUG
級別,僅記錄 payload 的根元素。在 TRACE
級別,記錄整個訊息內容。如果只想記錄傳送的訊息,請使用 org.springframework.ws.server.MessageTracing.sent
logger。類似地,可以使用 org.springframework.ws.server.MessageTracing.received
記錄接收到的訊息。
在客戶端,存在類似的 logger:org.springframework.ws.client.MessageTracing.sent
和 org.springframework.ws.client.MessageTracing.received
。
以下是 log4j.properties
配置檔案的示例,它記錄客戶端傳送訊息的完整內容,以及客戶端接收訊息的 payload 根元素。在伺服器端,傳送和接收訊息的 payload 根元素都會被記錄
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,並具有可配置的 endpoint 對映、響應生成和 endpoint 攔截。Endpoint 通常使用 @Endpoint
註解進行註解,並具有一個或多個處理方法。這些方法透過檢查訊息的部分(通常是 payload)來處理入站 XML 請求訊息,並建立某種響應。你可以使用另一個註解(通常是 @PayloadRoot
)對方法進行註解,以指示它可以處理哪種訊息。
Spring-WS 的 XML 處理非常靈活。endpoint 可以從 Spring-WS 支援的大量 XML 處理庫中進行選擇,包括
-
DOM 系列:W3C DOM、JDOM、dom4j 和 XOM
-
SAX 或 StAX:用於更快的效能
-
XPath:從訊息中提取資訊
-
編組技術(JAXB、Castor、XMLBeans、JiBX 或 XStream):將 XML 轉換為物件,反之亦然
5.1. MessageDispatcher
Spring-WS 的伺服器端圍繞一箇中心類設計,該類將入站 XML 訊息分派到 endpoint。Spring-WS 的 MessageDispatcher
非常靈活,允許你使用任何型別的類作為 endpoint,只要它可以在 Spring IoC 容器中配置。在某種程度上,訊息排程器類似於 Spring 的 DispatcherServlet
,即 Spring Web MVC 中使用的“前端控制器”。
以下序列圖顯示了 MessageDispatcher
的處理和分派流程

當 MessageDispatcher
配置好並有一個請求到達該特定排程器時,MessageDispatcher
開始處理請求。以下過程描述了 MessageDispatcher
如何處理請求
-
搜尋已配置的
EndpointMapping(s)
以查詢合適的 endpoint。如果找到 endpoint,則呼叫與該 endpoint 關聯的呼叫鏈(pre-processor、post-processor 和 endpoint)以建立響應。 -
為 endpoint 找到一個合適的 adapter。
MessageDispatcher
將呼叫委託給此 adapter 以呼叫 endpoint。 -
如果返回響應,則將其傳送出去。如果未返回響應(例如,由於 pre-processor 或 post-processor 攔截了請求,出於安全原因等),則不傳送響應。
在處理請求過程中丟擲的異常會被應用程式上下文中宣告的任何 endpoint 異常解析器捕獲。使用這些異常解析器,你可以在丟擲此類異常時定義自定義行為(例如返回 SOAP fault)。
MessageDispatcher
有幾個用於設定 endpoint adapter、mapping、exception resolver 的屬性。但是,設定這些屬性不是必需的,因為排程器會自動檢測應用程式上下文中註冊的所有型別。只有在需要覆蓋檢測時,才應設定這些屬性。
訊息排程器在訊息上下文上操作,而不是在特定於傳輸的輸入流和輸出流上操作。因此,需要將特定於傳輸的請求讀入 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,例如 endpoint、marshaller 等。
作為 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
的程式設計配置的更多詳細資訊,請參閱 AbstractMessageDispatcherServletInitializer
和 AbstractAnnotationConfigMessageDispatcherServletInitializer
的 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 傳送 GET
請求(請適當替換主機、埠和 servlet 上下文路徑)來訪問 classpath 中 orders.wsdl
檔案中定義的 WSDL
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 schema 生成 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 schema 構建 WSDL。它遍歷 schema 中找到的所有 element
元素,併為所有元素建立一個 message
。接下來,它為所有以定義的請求或響應字尾結尾的訊息建立一個 WSDL operation
。預設的請求字尾是 Request
。預設的響應字尾是 Response
,不過可以透過在 <dynamic-wsdl />
上分別設定 requestSuffix
和 responseSuffix
屬性來更改。它還根據操作構建一個 portType
、binding
和 service
。
例如,如果我們的 Orders.xsd
schema 定義了 GetOrdersRequest
和 GetOrdersResponse
元素,<dynamic-wsdl>
會建立一個 GetOrdersRequest
和 GetOrdersResponse
訊息以及一個 GetOrders
操作,該操作被放入一個 Orders
port type 中。
要使用多個 schema(無論是透過 include 還是 import),您可以將 Commons XMLSchema 放在 classpath 上。如果 Commons XMLSchema 在 classpath 上,<dynamic-wsdl>
元素會遵循所有 XSD 的 import 和 include,並將它們作為單個 XSD 內聯到 WSDL 中。這極大地簡化了 schema 的部署,同時仍然可以單獨編輯它們。
儘管在執行時從 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
中。此訊息監聽器需要 WebServiceMessageFactory
和 MessageDispatcher
才能執行。以下配置示例展示了這一點
<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
配置主機名,storeUri
指示要監視請求的郵件資料夾(通常是 POP3 或 IMAP 資料夾),以及 transportUri
,transportUri
指示用於傳送響應的伺服器(通常是 SMTP 伺服器)。
您可以使用可插入策略配置 MailMessageReceiver
如何監視傳入訊息:即 MonitoringStrategy
。預設情況下使用輪詢策略,即每五分鐘輪詢一次收件箱是否有新訊息。您可以透過設定策略上的 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 <[email protected]>"/>
<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
)。您只需要定義一個伺服器例項並配置它來處理傳入請求。Spring 核心框架中的 remoting 模組包含一個方便的 HTTP 伺服器工廠 bean:SimpleHttpServerFactoryBean
。最重要的屬性是 contexts
,它將上下文路徑對映到相應的 HttpHandler
例項。
Spring Web Services 提供了 HttpHandler
介面的兩種實現:WsdlDefinitionHttpHandler
和 WebServiceMessageReceiverHttpHandler
。前者將傳入的 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
的 XmppMessageSender
和一個用於 MessageDispatcher
的 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.3. 端點
端點是 Spring-WS 伺服器端支援的核心概念。端點提供對應用程式行為的訪問,這些行為通常由業務服務介面定義。端點解釋 XML 請求訊息,並使用該輸入(通常)呼叫業務服務上的方法。該服務呼叫的結果表示為響應訊息。Spring-WS 有各種各樣的端點,並使用不同的方式來處理 XML 訊息和建立響應。
您可以透過使用 @Endpoint
註解標記類來建立一個端點。在該類中,您可以定義一個或多個方法來處理傳入的 XML 請求,使用各種引數型別(例如 DOM 元素、JAXB2 物件等)。您可以使用另一個註解(通常是 @PayloadRoot
)來指示方法可以處理哪種訊息。
考慮以下示例端點
package samples;
@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 註解)作為引數。這意味著訊息的 payload 以 DOM 元素的形式傳遞給此方法。該方法的返回型別為 void ,表示不傳送響應訊息。有關端點方法的更多資訊,請參閱 @Endpoint 處理方法。 |
4 | getOrder 方法接受一個 OrderRequest (也使用 @RequestPayload 註解)作為引數。此引數是 JAXB2 支援的物件(它使用 @XmlRootElement 註解)。這意味著訊息的 payload 作為反序列化物件傳遞給此方法。SoapHeader 型別也作為引數給出。呼叫時,此引數包含請求訊息的 SOAP header。該方法也使用 @ResponsePayload 註解,指示返回值(即 Order )用作響應訊息的 payload。有關端點方法的更多資訊,請參閱 @Endpoint 處理方法。 |
5 | 此端點的兩個處理方法都使用 @PayloadRoot 標記,指示該方法可以處理哪種請求訊息:對於 payload 根元素的本地名稱為 orderRequest 且 namespace URI 為 http://samples 的請求,將呼叫 getOrder 方法。對於 payload 根元素的本地名稱為 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 一樣,預設情況下範圍為 singleton。也就是說,每個容器會建立一個 bean 定義的例項。作為 singleton 意味著多個執行緒可以同時使用它,因此端點必須是執行緒安全的。如果您想使用不同的範圍,例如 prototype,請參閱 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 訊息,它需要有一個或多個處理方法。處理方法可以接受各種引數和返回型別。然而,它們通常有一個包含訊息 payload 的引數,並返回響應訊息的 payload(如果有)。本節介紹了支援的引數和返回型別。
要指示方法可以處理哪種訊息,該方法通常使用 @PayloadRoot
或 @SoapAction
註解進行標記。您可以在 端點對映 中瞭解有關這些註解的更多資訊。
以下示例展示了一個處理方法
@PayloadRoot(localPart = "order", namespace = "http://samples")
public void order(@RequestPayload Element orderElement) {
Order order = createOrder(orderElement);
orderService.createOrder(order);
}
order
方法接受一個 Element
(使用 @RequestPayload
註解)作為引數。這意味著訊息的 payload 以 DOM 元素的形式傳遞給此方法。該方法的返回型別為 void
,表示不傳送響應訊息。
處理方法引數
處理方法通常有一個或多個引數,這些引數指向傳入 XML 訊息的不同部分。最常見的情況是,處理方法有一個引數對映到訊息的 payload,但它也可以對映到請求訊息的其他部分,例如 SOAP header。本節描述了您可以在處理方法簽名中使用的引數。
要將引數對映到請求訊息的 payload,您需要使用 @RequestPayload
註解標記此引數。此註解告訴 Spring-WS 該引數需要繫結到請求 payload。
下表描述了支援的引數型別。它顯示了支援的型別、引數是否應使用 @RequestPayload
註解以及任何附加說明。
名稱 | 支援的引數型別 | @RequestPayload 需要嗎? |
附加說明 |
---|---|---|---|
TrAX |
|
是 |
預設啟用。 |
W3C DOM |
|
是 |
預設啟用 |
dom4j |
|
是 |
當 classpath 中存在 dom4j 時啟用。 |
JDOM |
|
是 |
當 classpath 中存在 JDOM 時啟用。 |
XOM |
|
是 |
當 classpath 中存在 XOM 時啟用。 |
StAX |
|
是 |
當 classpath 中存在 StAX 時啟用。 |
XPath |
任何 boolean、double、 |
否 |
預設啟用,請參閱 |
訊息上下文 |
|
否 |
預設啟用。 |
SOAP |
|
否 |
預設啟用。 |
JAXB2 |
任何使用 |
是 |
當 classpath 中存在 JAXB2 時啟用。 |
OXM |
Spring OXM |
是 |
指定 |
接下來幾個示例展示了可能的方法簽名。以下方法以請求訊息的 payload 作為 DOM org.w3c.dom.Element
進行呼叫
public void handle(@RequestPayload Element element)
以下方法以請求訊息的 payload 作為 javax.xml.transform.dom.DOMSource
進行呼叫。header
引數繫結到請求訊息的 SOAP header。
public void handle(@RequestPayload DOMSource domSource, SoapHeader header)
以下方法以請求訊息的 payload 反序列化到 MyJaxb2Object
中(使用 @XmlRootElement
註解)進行呼叫。訊息的 payload 也作為 DOM Element
提供。整個訊息上下文作為第三個引數傳遞。
public void handle(@RequestPayload MyJaxb2Object requestObject, @RequestPayload Element element, Message messageContext)
如您所見,在定義如何處理方法簽名時有很多可能性。您甚至可以擴充套件此機制以支援您自己的引數型別。查閱 DefaultMethodEndpointAdapter
和 MethodArgumentResolver
的 Javadoc,瞭解如何實現。
@XPathParam
一種引數型別需要一些額外的解釋:@XPathParam
。這裡的想法是,您可以使用 XPath 表示式註解一個或多個方法引數,並且每個這樣的註解引數都繫結到表示式的求值結果。以下示例展示瞭如何實現
package samples;
@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。這可以透過 @Namespace
註解來實現。或者,我們可以將此註解放在型別級別,以便所有處理方法都使用相同的 namespace 對映,甚至可以放在包級別(在 package-info.java
中),以便多個端點使用。
透過使用 @XPathParam
,您可以繫結到 XPath 支援的所有資料型別
-
boolean
或Boolean
-
double
或Double
-
String
-
Node
-
NodeList
除了此列表之外,您還可以使用任何可以透過 Spring 轉換服務從 String
轉換的型別。
處理方法返回型別
要傳送響應訊息,處理方法需要指定返回型別。如果不需要響應訊息,方法可以宣告 void
返回型別。最常見的是,返回型別用於建立響應訊息的 payload。然而,您也可以對映到響應訊息的其他部分。本節描述了您可以在處理方法簽名中使用的返回型別。
要將返回值對映到響應訊息的 payload,您需要使用 @ResponsePayload
註解標記該方法。此註解告訴 Spring-WS 返回值需要繫結到響應 payload。
下表描述了支援的返回型別。它顯示了支援的型別、引數是否應使用 @ResponsePayload
註解以及任何附加說明。
名稱 | 支援的返回型別 | @ResponsePayload 需要嗎? |
附加說明 |
---|---|---|---|
無響應 |
|
否 |
預設啟用。 |
TrAX |
|
是 |
預設啟用。 |
W3C DOM |
|
是 |
預設啟用 |
dom4j |
|
是 |
當 classpath 中存在 dom4j 時啟用。 |
JDOM |
|
是 |
當 classpath 中存在 JDOM 時啟用。 |
XOM |
|
是 |
當 classpath 中存在 XOM 時啟用。 |
JAXB2 |
任何使用 |
是 |
當 classpath 中存在 JAXB2 時啟用。 |
OXM |
Spring OXM |
是 |
指定 |
在定義處理方法簽名時有很多可能性。甚至可以擴充套件此機制以支援您自己的引數型別。查閱 DefaultMethodEndpointAdapter
和 MethodReturnValueHandler
的類級別 Javadoc,瞭解如何實現。
5.4. 端點對映
端點對映負責將傳入訊息對映到適當的端點。預設啟用了一些端點對映,例如 PayloadRootAnnotationMethodEndpointMapping
或 SoapActionAnnotationMethodEndpointMapping
。然而,我們首先需要了解 EndpointMapping
的一般概念。
一個 EndpointMapping
提供一個 EndpointInvocationChain
,其中包含與傳入請求匹配的端點,並且可能還包含應用於請求和響應的端點攔截器列表。當請求傳入時,MessageDispatcher
將其交給端點對映,讓它檢查請求並生成適當的 EndpointInvocationChain
。然後 MessageDispatcher
呼叫鏈中的端點和任何攔截器。
可配置的端點對映的概念,它可以選擇性地包含攔截器(攔截器又可以操作請求、響應或兩者),這是非常強大的。許多支援功能可以構建到自定義的 EndpointMapping
實現中。例如,自定義端點對映不僅可以根據訊息內容選擇端點,還可以根據特定的 SOAP header(或者實際上是多個 SOAP header)來選擇端點。
大多數端點對映繼承自 AbstractEndpointMapping
,該類提供了一個“interceptors”屬性,它是要使用的攔截器列表。 攔截請求——EndpointInterceptor
介面 中討論了 EndpointInterceptors
。此外,還有一個 defaultEndpoint
,當此端點對映未能找到匹配的端點時使用它作為預設端點。
如 端點 中所述,@Endpoint
風格允許您在一個端點類中處理多個請求。這是 MethodEndpointMapping
的職責。此對映決定了應為傳入請求訊息呼叫哪個方法。
有兩種端點對映可以將請求導向方法:PayloadRootAnnotationMethodEndpointMapping
和 SoapActionAnnotationMethodEndpointMapping
。您可以透過在應用程式上下文中使用 <sws:annotation-driven/>
來啟用這兩種方法。
PayloadRootAnnotationMethodEndpointMapping
使用 @PayloadRoot
註解,以及 localPart
和 namespace
元素,來標記具有特定限定名的方法。每當傳入訊息的 payload 根元素具有此限定名時,就會呼叫該方法。有關示例,請參閱 上文。
或者,SoapActionAnnotationMethodEndpointMapping
使用 @SoapAction
註解來標記具有特定 SOAP Action 的方法。每當傳入訊息帶有此 SOAPAction
header 時,就會呼叫該方法。
5.4.1. WS-Addressing
WS-Addressing 指定了一種傳輸中立的路由機制。它基於 To
和 Action
SOAP header,它們分別指示 SOAP 訊息的目的地和意圖。此外,WS-Addressing 允許您定義返回地址(用於普通訊息和故障)以及唯一的 message identifier,可用於關聯。有關 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
,而 action 設定為 http://example.com/fabrikam/mail/Delete
。此外,還有一個 message identifier 和一個 reply-to 地址。預設情況下,此地址是“匿名”地址,表示應使用與請求相同的通道(即 HTTP 響應)傳送響應,但它也可以是另一個地址,如本示例所示。
在 Spring Web Services 中,WS-Addressing 被實現為端點對映。透過使用此對映,您可以將 WS-Addressing action 與端點關聯,類似於前面描述的 SoapActionAnnotationMethodEndpointMapping
。
使用 AnnotationActionEndpointMapping
AnnotationActionEndpointMapping
類似於 SoapActionAnnotationMethodEndpointMapping
,但它使用 WS-Addressing header 而不是 SOAP Action 傳輸 header。
要使用 AnnotationActionEndpointMapping
,請使用 @Action
註解標記處理方法,類似於 @Endpoint
處理方法 和 端點對映 中描述的 @PayloadRoot
和 @SoapAction
註解。以下示例展示瞭如何實現
package samples;
@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 Action
為 http://samples/RequestOrder
的請求路由到 getOrder
方法。WS-Addressing Action
為 http://samples/CreateOrder
的請求路由到 order
方法。
預設情況下,AnnotationActionEndpointMapping
支援 1.0(2006 年 5 月)和 2004 年 8 月版本的 WS-Addressing。這兩個版本最流行,並且可以與 Axis 1 和 2、JAX-WS、XFire、Windows Communication Foundation (WCF) 和 Windows Services Enhancements (WSE) 3.0 互操作。如果需要,可以將規範的特定版本注入到 versions
屬性中。
除了 @Action
註解之外,您還可以使用 @Address
註解標記類。如果設定了此註解,其值將與傳入訊息的 To
header 屬性進行比較。
最後,還有 messageSenders
屬性,它是將響應訊息傳送到非匿名、帶外地址所必需的。您可以在此屬性中設定 MessageSender
實現,就像在 WebServiceTemplate
上設定一樣。請參閱 URI 和傳輸。
5.4.2. 攔截請求——EndpointInterceptor
介面
端點對映機制具有端點攔截器的概念。當您想對某些請求應用特定功能時,這些攔截器會非常有用——例如,處理與安全相關的 SOAP header 或請求和響應訊息的日誌記錄。
端點攔截器通常透過在應用程式上下文中使用 <sws:interceptors>
元素來定義。在此元素中,您可以定義適用於該應用程式上下文中定義的所有端點的端點攔截器 bean。或者,您可以使用 <sws:payloadRoot>
或 <sws:soapAction>
元素來指定攔截器應適用於哪個 payload root 名稱或 SOAP action。以下示例展示瞭如何實現
<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
),它攔截所有請求和響應。我們還定義了一個攔截器,它僅適用於 payload root namespace 為 http://www.example.com
的 XML 訊息。除了 namespaceUri
,我們還可以定義一個 localPart
屬性,以進一步限制攔截器適用的訊息。最後,我們定義了兩個攔截器,當訊息具有 http://www.example.com/SoapAction
SOAP action 時,它們將應用。注意第二個攔截器實際上是對 <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(..)
方法返回一個 boolean 值。您可以使用此方法中斷或繼續呼叫鏈的處理。當此方法返回 true
時,端點處理鏈將繼續。當它返回 false
時,MessageDispatcher
將其解釋為攔截器本身已處理了事務,並且不再繼續處理呼叫鏈中的其他攔截器和實際端點。handleResponse(..)
和 handleFault(..)
方法也具有 boolean 返回值。當這些方法返回 false
時,響應將不會發送回客戶端。
您可以在 Web 服務中使用許多標準的 EndpointInterceptor
實現。此外,還有 XwsSecurityInterceptor
,其描述見 XwsSecurityInterceptor
。
PayloadLoggingInterceptor
和 SoapEnvelopeLoggingInterceptor
開發 Web 服務時,記錄傳入和傳出的 XML 訊息會很有用。Spring WS 透過 PayloadLoggingInterceptor
和 SoapEnvelopeLoggingInterceptor
類提供了便利。前者僅將訊息的 payload 記錄到 Commons Logging Log 中。後者記錄整個 SOAP envelope,包括 SOAP header。以下示例展示瞭如何在端點對映中定義 PayloadLoggingInterceptor
<sws:interceptors>
<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
</sws:interceptors>
這兩個攔截器都有兩個屬性,logRequest
和 logResponse
,可以設定為 false
以停用請求或響應訊息的日誌記錄。
您也可以使用前面描述的 WsConfigurerAdapter
方法來實現 PayloadLoggingInterceptor
。
PayloadValidatingInterceptor
使用契約優先開發風格的好處之一是我們可以使用 schema 來驗證傳入和傳出的 XML 訊息。Spring-WS 透過 PayloadValidatingInterceptor
提供了便利。此攔截器需要引用一個或多個 W3C XML 或 RELAX NG schema,並且可以設定為驗證請求、響應或兩者。
注意,請求驗證聽起來可能是一個好主意,但這會使結果的 Web 服務非常嚴格。通常,請求是否驗證透過並不真正重要,只有端點能否獲得足夠的資訊來滿足請求才重要。驗證響應是一個好主意,因為端點應該遵守其模式。記住 Postel 定律:“嚴於律己,寬以待人。” |
以下示例使用了 PayloadValidatingInterceptor
。在此示例中,我們使用 /WEB-INF/orders.xsd
中的模式來驗證響應,但不驗證請求。注意,PayloadValidatingInterceptor
也可以透過設定 schemas
屬性接受多個模式。
<bean id="validatingInterceptor"
class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="schema" value="/WEB-INF/orders.xsd"/>
<property name="validateRequest" value="false"/>
<property name="validateResponse" value="true"/>
</bean>
當然,你也可以像前面描述的那樣,對 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 提供了 EndpointExceptionResolvers
,以減輕在匹配請求的端點處理訊息時發生的意外異常帶來的痛苦。端點異常解析器在某種程度上類似於可以在 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 Fault
<beans>
<bean id="exceptionResolver"
class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
<property name="defaultFault" value="SERVER"/>
<property name="exceptionMappings">
<value>
org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request
</value>
</property>
</bean>
</beans>
鍵值和預設端點使用 faultCode,faultString,locale
格式,其中只需要錯誤程式碼。如果未設定錯誤字串,則預設為異常訊息。如果未設定語言,則預設為英語。前面的配置將 ValidationFailureException
型別的異常對映到客戶端 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;
@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
的典型用法是:。
-
透過呼叫
MockWebServiceClient.createClient(ApplicationContext)
或MockWebServiceClient.createClient(WebServiceMessageReceiver, WebServiceMessageFactory)
建立一個MockWebServiceClient
例項。 -
透過呼叫
sendRequest(RequestCreator)
傳送請求訊息,可能使用RequestCreators
中提供的預設RequestCreator
實現(可以靜態匯入)。 -
透過呼叫
andExpect(ResponseMatcher)
設定響應期望,可能使用ResponseMatchers
中提供的預設ResponseMatcher
實現(可以靜態匯入)。可以透過鏈式呼叫andExpect(ResponseMatcher)
設定多個期望。
注意,MockWebServiceClient (及相關類)提供了“流暢”API,因此你通常可以使用 IDE 中的程式碼補全功能來指導你完成設定模擬伺服器的過程。 |
另請注意,在單元測試中,你可以依賴 Spring Web Services 中提供的標準日誌記錄功能。有時,檢查請求或響應訊息以找出特定測試失敗的原因可能很有用。有關更多資訊,請參閱 訊息日誌記錄和跟蹤。 |
例如,考慮以下 Web 服務端點類
@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 。這兩個類都是由 Marshaller 支援的物件。例如,它們可以有一個 @XmlRootElement 註解以被 JAXB2 支援。 |
以下示例顯示了 CustomerEndpoint
的典型測試
@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 並靜態匯入了 RequestCreators 和 ResponseMatchers 。 |
2 | 此測試使用了 Spring Framework 中提供的標準測試工具。這不是必需的,但通常是設定測試的最簡單方法。 |
3 | 應用程式上下文是一個標準的 Spring-WS 應用程式上下文(參見 MessageDispatcherServlet ),從 spring-ws-servlet.xml 讀取。在這種情況下,應用程式上下文包含 CustomerEndpoint 的 bean 定義(或者可能使用了 <context:component-scan /> )。 |
4 | 在 @Before 方法中,我們使用 createClient 工廠方法建立了一個 MockWebServiceClient 。 |
5 | 我們透過呼叫 sendRequest() 傳送請求,使用靜態匯入的 RequestCreators 提供的 withPayload() RequestCreator (參見 使用 RequestCreator 和 RequestCreators )。我們還透過呼叫 測試的這一部分可能看起來有點令人困惑,但你的 IDE 的程式碼補全功能會很有幫助。在輸入 |
5.6.2. 使用 RequestCreator
和 RequestCreators
最初,MockWebServiceClient
需要為端點建立一個供其消費的請求訊息。客戶端為此目的使用了 RequestCreator
策略介面。
public interface RequestCreator {
WebServiceMessage createRequest(WebServiceMessageFactory messageFactory)
throws IOException;
}
你可以編寫自己的此介面實現,使用訊息工廠建立一個請求訊息,但你當然不必這樣做。RequestCreators
類提供了一種方法,可以在 withPayload()
方法中基於給定的負載建立 RequestCreator
。你通常會靜態匯入 RequestCreators
。
5.6.3. 使用 ResponseMatcher
和 ResponseMatchers
當請求訊息被端點處理並收到響應後,MockWebServiceClient
可以驗證此響應訊息是否符合某些期望。客戶端為此目的使用了 ResponseMatcher
策略介面。
public interface ResponseMatcher {
void match(WebServiceMessage request,
WebServiceMessage response)
throws IOException, AssertionError;
}
再次,你可以編寫自己的此介面實現,當訊息不符合你的期望時丟擲 AssertionError
例項,但你當然不必這樣做,因為 ResponseMatchers
類提供了標準的 ResponseMatcher
實現供你在測試中使用。你通常會靜態匯入此類。
ResponseMatchers
類提供了以下響應匹配器。
ResponseMatchers 方法 |
描述 |
---|---|
|
期望給定的響應負載。 |
|
期望響應負載能夠根據給定的 XSD 模式進行驗證。 |
|
期望給定的 XPath 表示式存在、不存在或計算為給定的值。 |
|
期望響應訊息中存在給定的 SOAP 頭部。 |
|
期望響應訊息不包含 SOAP Fault。 |
|
期望響應訊息包含特定的 SOAP Fault。 |
你可以透過鏈式呼叫 andExpect()
設定多個響應期望。
mockClient.sendRequest(...).
andExpect(payload(expectedResponsePayload)).
andExpect(validPayload(schemaResource));
有關 ResponseMatchers
提供的響應匹配器的更多資訊,請參閱 Javadoc。
6. 在客戶端使用 Spring Web Services
Spring-WS 提供了一個客戶端 Web 服務 API,允許以一致的、XML 驅動的方式訪問 Web 服務。它還支援使用 marshaller 和 unmarshaller,以便你的服務層程式碼可以完全處理 Java 物件。
org.springframework.ws.client.core
包提供了使用客戶端訪問 API 的核心功能。它包含簡化 Web 服務使用的模板類,就像 Spring 核心的 JdbcTemplate
對 JDBC 所做的那樣。Spring 模板類共同的設計原則是提供輔助方法來執行常見操作,對於更復雜的用法,則委託給使用者實現的 Callback 介面。Web 服務模板遵循相同的設計。這些類為以下方面提供了各種便利方法。
-
傳送和接收 XML 訊息
-
在傳送前將物件編組(Marshalling)為 XML
-
允許多種傳輸選項
6.1. 使用客戶端 API
本節描述如何使用客戶端 API。有關如何使用伺服器端 API,請參見 使用 Spring-WS 建立 Web 服務。
6.1.1. WebServiceTemplate
WebServiceTemplate
是 Spring-WS 中客戶端 Web 服務訪問的核心類。它包含傳送 Source
物件以及將響應訊息接收為 Source
或 Result
的方法。此外,它可以在透過傳輸傳送物件之前將物件編組(Marshal)為 XML,並將任何響應 XML 再次解組(Unmarshal)為物件。
URI 和傳輸
WebServiceTemplate
類使用 URI 作為訊息目標。你可以在模板本身上設定 defaultUri
屬性,或在呼叫模板方法時顯式提供 URI。URI 被解析為 WebServiceMessageSender
,它負責透過傳輸層傳送 XML 訊息。你可以使用 WebServiceTemplate
類的 messageSender
或 messageSenders
屬性設定一個或多個訊息傳送器。
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
,將其傳送到 Queue
或 Topic
上,並接收響應(如果有)。
要使用 JmsMessageSender
,你需要將 defaultUri
或 uri
引數設定為 JMS URI,它至少包含 jms:
字首和一個目標名稱。一些 JMS URI 示例包括:jms:SomeQueue
、jms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENT
和 jms:RequestQueue?replyToName=ResponseName
。有關此 URI 語法的更多資訊,請參見 JmsMessageSender
的 Javadoc。
預設情況下,JmsMessageSender
傳送 JMS BytesMessage
,但你可以透過使用 JMS URI 上的 messageType
引數來覆蓋此設定以使用 TextMessages
,例如 jms:Queue?messageType=TEXT_MESSAGE
。請注意,BytesMessages
是首選型別,因為 TextMessages
不可靠地支援附件和字元編碼。
以下示例顯示瞭如何結合 ActiveMQ 連線工廠使用 JMS 傳輸。
<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
,請將 defaultUri
或 uri
引數設定為 mailto
URI,例如 mailto:[email protected]
或 mailto:server@localhost?subject=SOAP%20Test
。確保訊息傳送器使用 transportUri
正確配置,該 URI 指示用於傳送請求的伺服器(通常是 SMTP 伺服器),以及 storeUri
,該 URI 指示用於輪詢響應的伺服器(通常是 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 <[email protected]>"/>
<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
,請將 defaultUri
或 uri
引數設定為 xmpp
URI,例如 xmpp:[email protected]
。傳送器還需要一個 XMPPConnection
才能工作,這可以透過使用 org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean
方便地建立。
以下示例顯示瞭如何使用 XMPP 傳輸。
<beans>
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
<bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
<property name="host" value="jabber.org"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<constructor-arg ref="messageFactory"/>
<property name="messageSender">
<bean class="org.springframework.ws.transport.xmpp.XmppMessageSender">
<property name="connection" ref="connection"/>
</bean>
</property>
<property name="defaultUri" value="xmpp:[email protected]"/>
</bean>
</beans>
6.1.2. 傳送和接收 WebServiceMessage
WebServiceTemplate
包含許多方便的方法來發送和接收 Web 服務訊息。有些方法接受並返回 Source
,有些方法返回 Result
。此外,還有將物件編組(Marshal)和解組(Unmarshal)為 XML 的方法。以下示例傳送一個簡單的 XML 訊息到 Web 服務
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
向位於 https://:8080/WebService
的 Web 服務傳送“Hello, World”訊息(在 simpleSendAndReceive()
方法的情況下),並將結果寫入控制檯。WebServiceTemplate
被注入了預設 URI,該 URI 被使用是因為在 Java 程式碼中沒有顯式提供 URI。
注意,一旦配置完成(假設其所有依賴項也是執行緒安全的,Spring-WS 附帶的所有依賴項都是如此),WebServiceTemplate
類就是執行緒安全的,因此多個物件可以使用相同的共享 WebServiceTemplate
例項。WebServiceTemplate
暴露了一個無引數建構函式以及 messageFactory
和 messageSender
bean 屬性,你可以使用它們來構建例項(透過使用 Spring 容器或純 Java 程式碼)。或者,考慮派生自 Spring-WS 的 WebServiceGatewaySupport
便利基類,它暴露了方便的 bean 屬性以實現輕鬆配置。(你無需擴充套件此基類。它僅作為便利類提供。)
6.1.3. 傳送和接收 POJO — 編組(Marshalling)和解組(Unmarshalling)
為了方便傳送普通的 Java 物件,WebServiceTemplate
有許多 send(..)
方法,它們接受一個 Object
作為訊息資料內容的引數。WebServiceTemplate
類中的 marshalSendAndReceive(..)
方法將請求物件轉換為 XML 的任務委託給 Marshaller
,並將響應 XML 轉換為物件的任務委託給 Unmarshaller
。(有關編組(marshalling)和解組(unmarshaller)的更多資訊,請參見 Spring Framework 參考文件。)透過使用 marshaller,你的應用程式程式碼可以專注於正在傳送或接收的業務物件,而無需關心它如何表示為 XML 的細節。要使用編組功能,你必須使用 WebServiceTemplate
類的 marshaller
和 unmarshaller
屬性設定一個 marshaller 和一個 unmarshaller。
6.1.4. 使用 WebServiceMessageCallback
為了方便設定 SOAP 頭部和訊息上的其他設定,WebServiceMessageCallback
介面允許你在訊息建立後但在傳送前訪問它。以下示例演示瞭如何在透過編組(marshalling)物件建立的訊息上設定 SOAP action 頭部。
public void marshalWithSoapActionHeader(MyObject o) {
webServiceTemplate.marshalSendAndReceive(o, new WebServiceMessageCallback() {
public void doWithMessage(WebServiceMessage message) {
((SoapMessage)message).setSoapAction("http://tempuri.org/Action");
}
});
}
注意,你也可以使用 org.springframework.ws.soap.client.core.SoapActionCallback 來設定 SOAP action 頭部。 |
WS-Addressing
除了伺服器端 WS-Addressing 支援外,Spring Web Services 在客戶端也支援此規範。
要在客戶端設定 WS-Addressing 頭部,你可以使用 org.springframework.ws.soap.addressing.client.ActionCallback
。此 callback 接受所需的 action 頭部作為引數。它還有指定 WS-Addressing 版本和 To
頭部的建構函式。如果未指定,To
頭部預設為正在進行的連線的 URL。
以下示例將 Action
頭部設定為 http://samples/RequestOrder
webServiceTemplate.marshalSendAndReceive(o, new ActionCallback("http://samples/RequestOrder"));
6.1.5. 使用 WebServiceMessageExtractor
WebServiceMessageExtractor
介面是一個低階 callback 介面,你可以完全控制從接收到的 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 服務的類)時,你有兩種可能的方法。
-
編寫單元測試,它模擬(mock)掉
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
的典型用法是:。
-
透過呼叫
MockWebServiceServer.createServer(WebServiceTemplate)
、MockWebServiceServer.createServer(WebServiceGatewaySupport)
或MockWebServiceServer.createServer(ApplicationContext)
建立一個MockWebServiceServer
例項。 -
透過呼叫
expect(RequestMatcher)
設定請求期望,可能使用RequestMatchers
中提供的預設RequestMatcher
實現(可以靜態匯入)。可以透過鏈式呼叫andExpect(RequestMatcher)
設定多個期望。 -
透過呼叫
andRespond(ResponseCreator)
建立適當的響應訊息,可能使用ResponseCreators
中提供的預設ResponseCreator
實現(可以靜態匯入)。 -
正常使用
WebServiceTemplate
,可以直接使用或透過客戶端程式碼使用。 -
呼叫
MockWebServiceServer.verify()
以確保所有期望都已滿足。
注意,MockWebServiceServer (及相關類)提供了“流暢”API,因此你通常可以使用 IDE 中的程式碼補全功能來指導你完成設定模擬伺服器的過程。 |
另請注意,在單元測試中,你可以依賴 Spring Web Services 中提供的標準日誌記錄功能。有時,檢查請求或響應訊息以找出特定測試失敗的原因可能很有用。有關更多資訊,請參閱 訊息日誌記錄和跟蹤。 |
例如,考慮以下 Web 服務客戶端類。
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 是由 marshaller 支援的物件。例如,它可以有一個 @XmlRootElement 註解以被 JAXB2 支援。 |
3 | CustomerClient 使用 WebServiceGatewaySupport 提供的 WebServiceTemplate 將請求物件編組(marshal)為 SOAP 訊息併發送到 Web 服務。響應物件被解組(unmarshal)為 CustomerCountResponse 。 |
以下示例顯示了 CustomerClient
的典型測試。
@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 並靜態匯入了 RequestMatchers 和 ResponseCreators 。 |
2 | 此測試使用了 Spring Framework 中提供的標準測試工具。這不是必需的,但通常是設定測試的最簡單方法。 |
3 | CustomerClient 在 integration-test.xml 中配置,並透過 @Autowired 注入到此測試中。 |
4 | 在 @Before 方法中,我們使用 createServer 工廠方法建立了一個 MockWebServiceServer 。 |
5 | 我們透過呼叫 expect() 定義期望,使用靜態匯入的 RequestMatchers 提供的 payload() RequestMatcher (參見 使用 RequestMatcher 和 RequestMatchers )。我們還透過呼叫 測試的這一部分可能看起來有點令人困惑,但你的 IDE 的程式碼補全功能會很有幫助。在你輸入 |
6 | 我們在 CustomerClient 上呼叫 getCustomerCount() ,從而使用了 WebServiceTemplate 。此時模板已設定為“測試模式”,因此此方法呼叫不會建立真正的 (HTTP) 連線。我們還根據方法呼叫的結果進行了一些 JUnit 斷言。 |
7 | 我們在 MockWebServiceServer 上呼叫 verify() ,驗證預期的訊息是否確實被接收到。 |
6.2.2. 使用 RequestMatcher
和 RequestMatchers
為了驗證請求訊息是否符合某些期望,MockWebServiceServer
使用了 RequestMatcher
策略介面。此介面定義的契約如下。
public interface RequestMatcher {
void match(URI uri,
WebServiceMessage request)
throws IOException,
AssertionError;
}
你可以編寫自己的此介面實現,當訊息不符合你的期望時丟擲 AssertionError
異常,但你當然不必這樣做。RequestMatchers
類提供了標準的 RequestMatcher
實現供你在測試中使用。你通常會靜態匯入此類。
RequestMatchers
類提供了以下請求匹配器。
RequestMatchers 方法 |
描述 |
---|---|
|
期望任何型別的請求。 |
|
payload() |
|
期望請求負載能夠根據給定的 XSD 模式進行驗證。 |
|
期望給定的 XPath 表示式存在、不存在或計算為給定的值。 |
|
soapHeader() |
|
期望連線到給定的 URL。 |
你可以透過鏈式呼叫 andExpect()
設定多個請求期望。
mockServer.expect(connectionTo("http://example.com")).
andExpect(payload(expectedRequestPayload)).
andExpect(validPayload(schemaResource)).
andRespond(...);
有關 RequestMatchers
提供的請求匹配器的更多資訊,請參閱 Javadoc。
6.2.3. 使用 ResponseCreator
和 ResponseCreators
當請求訊息已驗證並符合定義的期望後,MockWebServiceServer
會建立一個響應訊息供 WebServiceTemplate
消費。伺服器為此目的使用了 ResponseCreator
策略介面。
public interface ResponseCreator {
WebServiceMessage createResponse(URI uri,
WebServiceMessage request,
WebServiceMessageFactory messageFactory)
throws IOException;
}
再次,你可以編寫自己的此介面實現,使用訊息工廠建立一個響應訊息,但你當然不必這樣做,因為 ResponseCreators
類提供了標準的 ResponseCreator
實現供你在測試中使用。你通常會靜態匯入此類。
ResponseCreators
類提供了以下響應。
ResponseCreators 方法 |
描述 |
---|---|
|
建立一個具有給定負載的響應訊息。 |
|
在響應連線中建立錯誤。此方法使你有機會測試你的錯誤處理。 |
|
從響應連線讀取時丟擲異常。此方法使你有機會測試你的異常處理。 |
|
建立一個帶有給定 SOAP 錯誤的響應訊息。此方法使你有機會測試你的 Fault 處理。 |
有關 RequestMatchers
提供的請求匹配器的更多資訊,請參閱 Javadoc。
7. 使用 Spring-WS 保護你的 Web 服務
本章解釋如何為你的 Web 服務新增 WS-Security 方面。我們重點關注 WS-Security 的三個不同領域。
-
認證(Authentication):這是一個確定主體是否是他們聲稱的身份的過程。在此上下文中,“主體”通常指使用者、裝置或可以在應用程式中執行操作的其他系統。
-
數字簽名(Digital signatures):訊息的數字簽名是基於文件和簽名者私鑰的一段資訊。它透過使用雜湊函式和私有簽名函式(使用簽名者的私鑰加密)建立。
-
加密和解密(Encryption and Decryption):加密是將資料轉換為沒有相應金鑰就無法讀取的形式的過程。它主要用於對非目標接收者隱藏資訊。解密是加密的反向過程。它是將加密資料轉換回可讀形式的過程。
這三個領域透過使用 XwsSecurityInterceptor
或 Wss4jSecurityInterceptor
實現,我們分別在 XwsSecurityInterceptor
和 使用 Wss4jSecurityInterceptor
中進行描述。
請注意,WS-Security(尤其是加密和簽名)需要大量的記憶體,並且可能會降低效能。如果效能對您很重要,您可能需要考慮不使用 WS-Security 或改用基於 HTTP 的安全性。 |
7.1. XwsSecurityInterceptor
XwsSecurityInterceptor
是一個 EndpointInterceptor
(請參閱攔截請求 — EndpointInterceptor
介面),它基於 SUN 的 XML 和 Web Services 安全包 (XWSS)。此 WS-Security 實現是 Java Web Services Developer Pack (Java WSDP) 的一部分。
與任何其他 Endpoint 攔截器一樣,它在 Endpoint 對映中定義(請參閱Endpoint 對映)。這意味著您可以選擇性地新增 WS-Security 支援。某些 Endpoint 對映需要它,而其他則不需要。
請注意,XWSS 需要 SUN 1.5 JDK 和 SUN SAAJ 參考實現。WSS4J 攔截器沒有這些要求(請參閱使用 Wss4jSecurityInterceptor )。 |
XwsSecurityInterceptor
需要一個安全策略檔案才能執行。這個 XML 檔案告訴攔截器需要從傳入的 SOAP 訊息中要求哪些安全方面,以及向傳出的訊息中新增哪些方面。以下部分解釋了策略檔案的基本格式,但您可以在此處找到更深入的教程。您可以使用 policyConfiguration
屬性設定策略,該屬性需要一個 Spring 資源。策略檔案可以包含多個元素——例如,要求傳入訊息具有使用者名稱令牌並對所有傳出訊息進行簽名。它包含一個 SecurityConfiguration
元素(而不是 JAXRPCSecurity
元素)作為其根元素。
此外,安全攔截器需要一個或多個 CallbackHandler
例項才能執行。這些處理器用於檢索證書、私鑰、驗證使用者憑據等。Spring-WS 為大多數常見的安全問題提供了處理器——例如,針對 Spring Security 身份驗證管理器進行身份驗證,以及基於 X509 證書對傳出訊息進行簽名。以下部分將說明針對特定安全問題使用哪個回撥處理器。您可以使用 callbackHandler
或 callbackHandlers
屬性設定回撥處理器。
以下示例展示瞭如何配置 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>
此攔截器透過 classpath 中的 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>
如果您不指定位置屬性,則會建立一個新的空金鑰庫,這很可能不是您想要的。 |
KeyStoreCallbackHandler
要在 XwsSecurityInterceptor
中使用金鑰庫,您需要定義一個 KeyStoreCallbackHandler
。此回撥處理器有三個型別為 keystore
的屬性:(keyStore
、trustStore
和 symmetricStore
)。處理器使用的具體金鑰庫取決於此處理器將執行的加密操作。對於私鑰操作,使用 keyStore
。對於對稱金鑰操作,使用 symmetricStore
。對於確定信任關係,使用 trustStore
。下表說明了這一點
加密操作 | 使用的金鑰庫 |
---|---|
證書驗證 |
首先 |
基於私鑰的解密 |
|
基於對稱金鑰的解密 |
|
基於公鑰證書的加密 |
|
基於對稱金鑰的加密 |
|
簽名 |
|
簽名驗證 |
|
此外,KeyStoreCallbackHandler
還有一個 privateKeyPassword
屬性,應將其設定為解鎖包含在 keyStore
中的私鑰。
如果未設定 symmetricStore
,則預設為 keyStore
。如果未設定 key 或 trust store,則回撥處理器使用標準的 Java 機制載入或建立它。請參閱 KeyStoreCallbackHandler
的 JavaDoc 以瞭解此機制如何工作。
例如,如果您想使用 KeyStoreCallbackHandler
驗證傳入的證書或簽名,您可以使用 trust store
<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>
如果您想使用它來解密傳入的證書或簽署傳出訊息,您可以使用 key store
<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 故障。如果存在,它會向註冊的處理器觸發一個帶有 PlainTextPasswordRequest
的 PasswordValidationCallback
。在 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 NameCallback
和 PasswordCallback
。這意味著此回撥處理器與在 login()
階段觸發這些回撥的任何 JAAS LoginModule
整合,這是標準行為。
您可以如下所示配置 JaasPlainTextPasswordValidationCallbackHandler
<bean id="jaasValidationHandler"
class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasPlainTextPasswordValidationCallbackHandler">
<property name="loginContextName" value="MyLoginModule" />
</bean>
在這種情況下,回撥處理器使用名為 MyLoginModule
的 LoginContext
。此模組應在您的 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 故障。如果存在,它會向註冊的處理器觸發一個帶有 DigestPasswordRequest
的 PasswordValidationCallback
。在 Spring-WS 中,有兩個類處理此特定回撥:SimplePasswordValidationCallbackHandler
和 SpringDigestPasswordValidationCallbackHandler
。
使用 SimplePasswordValidationCallbackHandler
SimplePasswordValidationCallbackHandler
可以處理明文密碼和密碼摘要。它在使用 SimplePasswordValidationCallbackHandler
中進行了描述。
使用 SpringDigestPasswordValidationCallbackHandler
SpringDigestPasswordValidationCallbackHandler
需要一個 Spring Security UserDetailService
才能執行。它使用此服務檢索令牌中指定使用者的密碼。然後將此 details 物件中包含的密碼摘要與訊息中的摘要進行比較。如果它們相等,則使用者已成功透過身份驗證,並且 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
,其中包含一個 Base 64 編碼的 X509 證書版本。接收方使用該證書進行身份驗證。訊息中儲存的證書也用於對訊息進行簽名(請參閱驗證簽名)。
為確保所有傳入的 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 術語中,這意味著
使用此設定,攔截器首先使用金鑰庫確定訊息中的證書是否有效,然後再對其進行身份驗證。 |
使用 KeyStoreCallbackHandler
KeyStoreCallbackHandler
使用標準的 Java 金鑰庫來驗證證書。此證書驗證過程包括以下步驟:.
-
處理器檢查證書是否在私有的
keyStore
中。如果在,則它是有效的。 -
如果證書不在私有金鑰庫中,處理器會檢查當前日期和時間是否在證書中給定的有效期內。如果不在,則證書無效。如果在,則繼續執行最後一步。
-
為證書建立認證路徑。這基本上意味著處理器確定該證書是否由
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>
使用前面示例中所示的設定,要驗證的證書必須在 trust store 本身中,或者 trust store 必須包含頒發該證書的證書頒發機構。
使用 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>
在這種情況下,回撥處理器使用名為 MyLoginModule
的 LoginContext
。此模組應在您的 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>
簽署訊息
簽署訊息時,XwsSecurityInterceptor
會將 BinarySecurityToken
新增到訊息中。它還新增一個 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
分別丟擲 WsSecuritySecurementException
或 WsSecurityValidationException
。這些異常繞過標準異常處理機制,但由攔截器自身處理。
WsSecuritySecurementException
異常由 XwsSecurityInterceptor
的 handleSecurementException
方法處理。預設情況下,此方法記錄錯誤並停止訊息的進一步處理。
類似地,WsSecurityValidationException
異常由 XwsSecurityInterceptor
的 handleValidationException
方法處理。預設情況下,此方法建立一個 SOAP 1.1 Client 或 SOAP 1.2 sender 故障,並將其作為響應傳送回去。
handleSecurementException 和 handleValidationException 都是受保護的方法,您可以覆蓋它們以更改其預設行為。 |
7.2. 使用 Wss4jSecurityInterceptor
Wss4jSecurityInterceptor
是一個 EndpointInterceptor
(請參閱攔截請求 — EndpointInterceptor
介面),它基於 Apache 的 WSS4J。
WSS4J 實現了以下標準
-
OASIS Web Services Security: SOAP Message Security 1.0 Standard 200401, March 2004
-
Username Token profile V1.0
-
X.509 Token Profile V1.0
此攔截器支援由 AxiomSoapMessageFactory
和 SaajSoapMessageFactory
建立的訊息。
7.2.1. 配置 Wss4jSecurityInterceptor
WSS4J 不使用外部配置檔案。攔截器完全透過屬性進行配置。此攔截器呼叫的驗證和安全增強操作分別透過 validationActions
和 securementActions
屬性指定。操作作為空格分隔的字串傳遞。以下清單展示了示例配置
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="UsernameToken Encrypt"/>
...
<property name="securementActions" value="Encrypt"/>
...
</bean>
下表顯示了可用的驗證操作
驗證操作 | 描述 |
---|---|
|
驗證使用者名稱令牌 |
|
驗證時間戳 |
|
解密訊息 |
|
驗證簽名 |
|
不執行任何操作 |
下表顯示了可用的安全增強操作
安全增強操作 | 描述 |
---|---|
|
新增使用者名稱令牌 |
|
新增使用者名稱令牌和使用者名稱令牌秘密金鑰簽名 |
|
新增時間戳 |
|
加密響應 |
|
簽署響應 |
|
不執行任何操作 |
操作的順序很重要,並由攔截器強制執行。如果其安全操作的執行順序與 validationActions
指定的順序不同,攔截器將拒絕傳入的 SOAP 訊息。
7.2.2. 處理數字證書
對於需要與金鑰庫或證書處理互動的加密操作(簽名、加密和解密操作),WSS4J 需要 org.apache.ws.security.components.crypto.Crypto
的例項。
可以從 WSS4J 的 CryptoFactory
或更方便地使用 Spring-WS 的 CryptoFactoryBean
獲取 Crypto
例項。
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
驗證明文和摘要密碼。它使用此服務檢索令牌中指定使用者的密碼(或密碼摘要)。然後將此 details 物件中包含的密碼(或密碼摘要)與訊息中的摘要進行比較。如果它們相等,則使用者已成功透過身份驗證,並且 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
新增到 Wss4jSecurityInterceptor
的 securementActions
屬性並指定 securementUsername
和 securementPassword
一樣簡單。
可以透過設定 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
屬性來指示攔截器新增 Nonce
和 Created
元素。該值必須是一個列表,其中包含所需元素的名稱,名稱之間用空格分隔(區分大小寫)。
以下示例生成一個帶有明文密碼、Nonce
和 Created
元素的使用者名稱令牌
<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
元素並將其包含在傳出訊息中。證書的名稱和密碼分別透過 securementUsername
和 securementPassword
屬性傳遞,如下例所示
<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
中可用的各種簽名選項。
驗證簽名
為了指導 Wss4jSecurityInterceptor
,validationActions
必須包含 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
,可以啟用對傳出訊息的簽名。要使用的私鑰的別名和密碼分別由 securementUsername
和 securementPassword
屬性指定。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
屬性來自定義要使用的金鑰識別符號型別。對於簽名,只有 IssuerSerial
和 DirectReference
是有效的。
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
來啟用簽名確認。請注意,簽名確認操作會跨越請求和響應。這意味著即使沒有相應的安全操作,secureResponse
和 validateRequest
也必須設定為 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
屬性定義。可能的值包括 IssuerSerial
、X509KeyIdentifier
、DirectReference
、Thumbprint
、SKIKeyIdentifier
和 EmbeddedKeyName
。
如果您選擇 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-cbc
、http://www.w3.org/2001/04/xmlenc#aes256-cbc
和 http://www.w3.org/2001/04/xmlenc#aes192-cbc
。
最後,securementEncryptionParts
屬性定義了對訊息的哪個部分進行加密。此屬性的值是一個以分號分隔的元素名稱列表,用於標識要加密的元素。每個元素名稱前面可以有一個加密模式指示符和一個名稱空間標識,每個都包含在一對花括號內。加密模式指示符可以是 {Content}
或 {Element}
。有關 Element 和 Content 加密之間的差異,請參閱 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
的異常處理相同。有關更多資訊,請參閱安全異常處理。
III. 其他資源
參考書目
-
[waldo-94] Jim Waldo, Ann Wollrath, and Sam Kendall. A Note on Distributed Computing. Springer Verlag. 1994
-
[alpine] Steve Loughran & Edmund Smith. Rethinking the Java SOAP Stack. May 17, 2005. © 2005 IEEE Telephone Laboratories, Inc.
-
[effective-enterprise-java] Ted Neward. Scott Meyers. Effective Enterprise Java. Addison-Wesley. 2004
-
[effective-xml] Elliotte Rusty Harold. Scott Meyers. Effective XML. Addison-Wesley. 2004