在建立 Web 服務時,有兩種開發風格:契約滯後和契約優先。 使用契約滯後方法時,您從 Java 程式碼開始,並讓 Web 服務契約(WSDL,參見側欄)由此生成。使用契約優先時,您從 WSDL 契約開始,並使用 Java 來實現該契約。
Spring-WS 僅支援契約優先的開發風格,本節將解釋原因。
與 ORM 領域類似,我們在那裡有一個物件/關係阻抗不匹配,在將 Java 物件轉換為 XML 時也存在類似的問題。乍一看,O/X 對映問題似乎很簡單:為每個 Java 物件建立一個 XML 元素,將所有 Java 屬性和欄位轉換為子元素或屬性。然而,事情並非看起來那麼簡單:像 XML(尤其是 XSD)這樣的分層語言與 Java 的圖形模型之間存在根本區別[1]。
在 Java 中,更改類行為的唯一方法是將其子類化,並將新行為新增到該子類。在 XSD 中,您可以透過限制它來擴充套件資料型別:也就是說,約束元素和屬性的有效值。例如,考慮以下示例
<simpleType name="AirportCode"> <restriction base="string"> <pattern value="[A-Z][A-Z][A-Z]"/> </restriction> </simpleType>
此型別透過正則表示式限制 XSD 字串,僅允許三個大寫字母。如果將此型別轉換為 Java,我們將得到一個普通的 java.lang.String
;正則表示式在轉換過程中丟失,因為 Java 不允許進行這些型別的擴充套件。
Web 服務最重要的目標之一是實現互操作性:支援 Java、.NET、Python 等多個平臺。因為所有這些語言都有不同的類庫,所以您必須使用一些通用的、跨語言的格式在它們之間進行通訊。該格式是 XML,所有這些語言都支援它。
由於此轉換,您必須確保在服務實現中使用可移植的型別。例如,考慮一個返回 java.util.TreeMap
的服務,如下所示
public Map getFlights() { // use a tree map, to make sure it's sorted TreeMap map = new TreeMap(); map.put("KL1117", "Stockholm"); ... return map; }
毫無疑問,此對映的內容可以轉換為某種 XML,但由於沒有在 XML 中描述對映的標準方法,因此它將是專有的。此外,即使它可以轉換為 XML,許多平臺也沒有類似於 TreeMap
的資料結構。因此,當 .NET 客戶端訪問您的 Web 服務時,它可能會最終得到一個 System.Collections.Hashtable
,它具有不同的語義。
在客戶端工作時也存在此問題。考慮以下描述服務契約的 XSD 程式碼段
<element name="GetFlightsRequest"> <complexType> <all> <element name="departureDate" type="date"/> <element name="from" type="string"/> <element name="to" type="string"/> </all> </complexType> </element>
此契約定義了一個接受 date 的請求,date 是一個表示年、月和日的 XSD 資料型別。如果我們從 Java 呼叫此服務,我們可能會使用 java.util.Date
或 java.util.Calendar
。然而,這兩個類實際上描述的是時間,而不是日期。因此,我們實際上最終會發送代表 2007 年 4 月 4 日午夜的資料 (2007-04-04T00:00:00
),這與 2007-04-04
不同。
假設我們有以下簡單的類結構
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
,後者再次引用 Flight
。像這樣的迴圈圖在 Java 中很常見。如果我們採用一種幼稚的方法將其轉換為 XML,我們將最終得到類似的東西
<flight number="KL1117"> <passengers> <passenger> <name>Arjen Poutsma</name> <flight number="KL1117"> <passengers> <passenger> <name>Arjen Poutsma</name> <flight number="KL1117"> <passengers> <passenger> <name>Arjen Poutsma</name> ...
這將花費很長時間才能完成,因為此迴圈沒有停止條件。
解決此問題的一種方法是使用對已編組的物件的引用,如下所示
<flight number="KL1117"> <passengers> <passenger> <name>Arjen Poutsma</name> <flight href="KL1117" /> </passenger> ... </passengers> </flight>
這解決了遞迴問題,但也引入了新的問題。首先,您不能使用 XML 驗證器來驗證此結構。另一個問題是,在 SOAP (RPC/encoded) 中使用這些引用的標準方法已被棄用,轉而支援 document/literal(請參閱 WS-I 基本配置檔案)。
這些只是處理 O/X 對映時遇到的一些問題。在編寫 Web 服務時,尊重這些問題非常重要。尊重它們的最佳方法是完全專注於 XML,同時使用 Java 作為實現語言。這就是契約優先的全部意義所在。
除了上一節中提到的物件/XML 對映問題之外,還有其他原因更傾向於契約優先的開發風格。
如前所述,契約滯後的開發風格導致您的 Web 服務契約(WSDL 和您的 XSD)從您的 Java 契約(通常是介面)生成。如果您使用這種方法,您將無法保證契約隨著時間的推移保持不變。每次您更改 Java 契約並重新部署它時,都可能會對 Web 服務契約進行後續更改。
此外,並非所有的 SOAP 堆疊都會從 Java 契約生成相同的 Web 服務契約。這意味著將您當前的 SOAP 堆疊更改為不同的 SOAP 堆疊(無論出於何種原因),也可能會更改您的 Web 服務契約。
當 Web 服務契約更改時,必須指示契約的使用者獲取新契約,並可能更改他們的程式碼以適應契約中的任何更改。
為了使契約有用,它必須儘可能長時間地保持不變。如果契約更改,您將必須聯絡您的服務的所有使用者,並指示他們獲取該契約的新版本。
當 Java 自動轉換為 XML 時,無法確定透過網路傳送的內容。一個物件可能會引用另一個物件,後者引用另一個物件,等等。最終,虛擬機器中堆上的一半物件可能會轉換為 XML,這將導致響應時間變慢。
使用契約優先時,您可以明確描述在何處傳送哪些 XML,從而確保它完全是您想要的。
在單獨的檔案中定義您的模式允許您在不同的場景中重用該檔案。如果您在一個名為 airline.xsd
的檔案中定義了一個 AirportCode,如下所示
<simpleType name="AirportCode"> <restriction base="string"> <pattern value="[A-Z][A-Z][A-Z]"/> </restriction> </simpleType>
您可以使用 import
語句在其他模式,甚至 WSDL 檔案中重用此定義。