建立 Web 服務有兩種開發方式:契約後置(Contract Last)和契約優先(Contract First)。當使用契約後置方法時,您從 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>此契約定義了一個接受日期的請求,這是一個表示年、月、日的 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,而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/編碼)已被棄用,取而代之的是文件/文字(請參閱 WS-I 基本配置檔案)。
這些只是處理 O/X 對映時的一些問題。在編寫 Web 服務時,尊重這些問題很重要。尊重它們的最佳方式是完全專注於 XML,同時使用 Java 作為實現語言。這正是契約優先的全部意義所在。
除了上一節中提到的物件/XML 對映問題之外,還有其他原因偏愛契約優先的開發方式。
如前所述,契約後置開發方式會導致您的 Web 服務契約(WSDL 和您的 XSD)從您的 Java 契約(通常是一個介面)生成。如果您使用這種方法,您將無法保證契約隨著時間的推移保持不變。每次您更改 Java 契約並重新部署時,Web 服務契約可能會隨之發生變化。
此外,並非所有 SOAP 棧都從 Java 契約生成相同的 Web 服務契約。這意味著出於任何原因更改當前的 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 檔案中重用此定義。