引言
該系列文章的第 I 部分討論了在編碼之前設計 Web 服務描述語言(Web Services Description Language,WSDL)和 XML Schema 數據類型(XML Schema data types,XSD)的重要性,完全轉換成文檔或文字式樣的基本原理,以及當開發 Web 服務的時候測試 WS-I Basic Profile 一致性的必要性。本文闡明了數據類型的用法及其對互操作性產生的影響。
Web 服務操作的輸入參數及數據類型的返回值對於 Web 服務的互操作性產生非常大的影響。Web 服務用作 XML 文檔轉換的傳送器。當數據對象被放入 Web 服務棧中時,它們被序列化成 XML 數據表示。另一方面,Web 服務棧需要准確地知道如何將那些 XML 數據表示映射到本地應用程序環境的需求中(例如 XML 數據的反序列化)。XML Schema 定義驅動了映射。XSD 的目的是確保發送的類型在其他終端有可復寫的版本。但是由於基本技術(企業版 Java 2 平台(Java™ 2 Platform,Enterprise Edition,J2EE)與 Mircosoft® .NET)的實現是不同的,所以 XSD 和那些平台上的本地數據類型之間的映射可能會不同。某些差異可能導致反序列化的失敗,而其它的可能導致信息失真。
在接下來的部分中,我將討論一些有關數據類型的互操作性的問題,例如:
提供商用於精確解釋 XML Schema 的工具是不存在的,XML Schema 代表弱類型的集合對象並將它們映射成正確的本地數據類型。
含有空元素的數組的 XML 表示不同於 .NET 和 IBM® WebSphere®。
由於缺乏本地和 XSD 數據類型所共享的一對一的映射,所以轉譯問題導致了信息的丟失或精度的降低。
在 Web 服務方法簽名中的復合數據類型集
集合對象可能包括任何數據類型的元素。因此,許多人把它們看作弱類型的數據結構。這使得它們成為非常好的編程工具。在面向對象的編程中,有大量的集合類型庫。例如,在 Java 中存在:
java.util.Hashtable
Vectors
Hashmap
Set
ArrayList
而在 C# 中存在:
System.Collections.Hashtable
SortedList
Queue
Stack
ArrayList
如果在整個 Web 服務中公布了這些集合類型,那麼它們可能引發不能被解決的問題。該問題是接收方如何能理解被序列化了的簡單對象訪問協議(Simple Object Access Protocol,SOAP)消息,這些消息中包含弱類型對象元素及本地數據類型。
即使一些集合類型看上去與某些語言非常相似,例如 C# 中的 System.Collections.ArrayList 及 Java 中的 java.util.ArrayList,記住集合中的元素是通用的參照。為了准確地解組集合的 XML 表示,客戶必須預先了解原始的具體類型。這個任務交給工具包開發人員來解釋 Web 服務提供者所發布的 XML Schemas 並將 SOAP 消息映射到本地數據中——不是對於弱類型集合的簡單任務。
現在,讓我們來看一看 Collection 類型的 XML Schemas 是什麼樣子。這次,考慮部署在 Microsoft .NET 框架上的 Web 服務。假設 InventoryService 接受 Product 的 System.Collections.ArrayList 作為變量,為 ArrayList 中的每個產品設置新價格(增長了百分之 10),並且返回 System.Collections.ArrayList 類型的新對象。
清單 1. 在 C# 中 Web 服務的詳細目錄
namespace Inventory
{
[WebService(Namespace="http://services.inventory")]
public class InventoryService: WebService
{
//increase the product price by 10 percent
private static float inc_rate = 0.10F;
public struct Product {
public string name;
public int qty;
public float price;
}
[WebMethod]
[XmlInclude(typeof(Product))]
public ArrayList updateProductPrice(ArrayList products)
{
ArrayList newList = new ArrayList();
IEnumerator eList = products.GetEnumerator();
while(eList.MoveNext())
{
Product item = (Product)(eList.Current);
item.price = item.price * (1 + inc_rate);
newList.Add(item);
}
return newList;
}
}
}
在 .NET 框架中的 WSDL 引擎生成了用於 Collection 類型、ArrayList 以及 Product 復合類型的如下的 XML Schema:
清單 2. 用於 ArrayList 和 Product 的 XML Schema
1. <types>
2. <s:schema xmlns:s="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://services.inventory">
3. <s:element name="updateProductPrice">
4. <s:complexType>
5. <s:sequence>
<s:element maxOccurs="1" minOccurs="0" name="products"
type="s0:ArrayOfAnyType"/>
6. </s:sequence>
7. </s:complexType>
8. </s:element>
9. <s:complexType name="ArrayOfAnyType">
10. <s:sequence>
11. <s:element maxOccurs="unbounded" minOccurs="0" name="anyType"
nillable="true"/>
12. </s:sequence>
13. </s:complexType>
14. <s:complexType name="Product">
15. <s:sequence>
16. <s:element maxOccurs="1" minOccurs="0" name="name" type="s:string"/>
17. <s:element maxOccurs="1" minOccurs="1" name="qty" type="s:int"/>
18. <s:element maxOccurs="1" minOccurs="1" name="price" type="s:float"/>
19. </s:sequence>
20. </s:complexType>
21. <s:element name="updateProductPriceResponse">
22. <s:complexType>
23. <s:sequence>
<s:element maxOccurs="1" minOccurs="0" name="updateProductPriceResult"
type="s0:ArrayOfAnyType"/>
24. </s:sequence>
25. </s:complexType>
26. </s:element>
27. </s:schema>
28. </types>
從第 9 行到第 13 行(詳見清單 2)定義了復合類型 xsd:ArrayOfAnyType,連同 anyType 元素的無界序列。Products 的 ArrayList 已經被翻譯成了 XML Schema 定義中的匿名元素序列。這是所期望的;但是,它也引發了兩個問題。首先,其它的 Collection 類型也將被翻譯成 xsd:ArrayOfAnyType。因此,在另一個平台上的 SOAP 工具包如何確定將它映射成哪種 Collection 類型?
其次,當沒有指定類型的時候 xsd:anyType 就是缺省的類型。清單 2 中的第 11 行是需要的,因為 Collection 中的對象是通用的參照——在運行之前並不知道類型。當在另一個平台上的 SOAP 工具包接收到序列化的對象時問題發生了。您如何找出正確的序列化器來將 XML 載荷反序列化到具體的對象中?
事實上,JAX-RPC 從清單 2 的 xsd:ArrayOfAnyType schema 中生成了如下的幫助類。
清單 3. 用於 xsd:ArrayOfAnyType schema 的作為結果的幫助類
public class ArrayOfAnyType implements java.io.Serializable {
private java.lang.Object[] anyType;
<!-- The setter, getter, equals() and hashCode() methods -->
}
從清單 3 中,您可以看到 xsd:ArrayOfAnyType schema 的不明確性已經導致 JAX-RPC 工具生成了幫助類。該幫助類將通用的 java.lang.Object[] 數組作為它的私有字段,取代了具體的 Product 數組。
為了消除這種不明確性,您可以使用 ArrayOfRealType 來代替 xsd:ArrayOfAnyType。您應當僅公布具體類型的簡單數組(也就是 Product[]),將其作為 Web 服務方法的簽名。
對於清單 1 中的 Web 服務,公布前端方法:
清單 4. 公布簡單數組 Product[] 的前端方法
[WebMethod]
[XmlInclude(typeof(Product))]
public Product[] updateProductPriceFacade(Product[] products)
{
ArrayList alist = new ArrayList();
IEnumerator it = products.GetEnumerator();
while (it.MoveNext())
alist.Add((Product)(it.Current));
alist = updateProductPrice(alist);
Product[] outArray = (Product[])alist.ToArray(typeof(Product));
return outArray;
}
對於輸入輸出消息部分的新 schemas 是:
清單 5. 對於清單 4 中的新的 Web 服務的 XML Schema
1. <s:element name="updateProductPriceFacade">
2. <s:complexType>
3. <s:sequence>
4. <s:element minOccurs="0" maxOccurs="1" name="products"
type="s0:ArrayOfProduct" />
5. </s:sequence>
6. </s:complexType>
7. </s:element>
8. <s:complexType name="ArrayOfProduct">
9. <s:sequence>
10. <s:element minOccurs="0" maxOccurs="unbounded" name="Product"
type="s0:Product" />
11. </s:sequence>
12. </s:complexType>
13. <s:element name="updateProductPriceFacadeResponse">
14. <s:complexType>
15. <s:sequence>
16. <s:element minOccurs="0" maxOccurs="1"
name="updateProductPriceFacadeResult" type="s0:ArrayOfProduct" />
17. </s:sequence>
18. </s:complexType>
19. </s:element>
從第 8 行到第 12 行,創建 xsd:ArrayOfProduct schema 來表示具體的 Product 數組。在 schema 中沒有出現不確定的內容。所以,最終 Web 服務客戶端在反序列化 Products 數組的過程中沒有遇到問題。
含有空元素的數組
含有空元素的數組的 XML 表示不同於 .NET 和 WebSphere。考慮清單 6 中所示的 Java Web 服務方法。
清單 6. 返回含有空元素的數組的 Java 方法
public String[] returnArrayWithNull() {
String[] s = new String[3];
s[0] = "ABC";
s[1] = null;
s[2] = "XYZ";
return s;
}
這裡的 String 元素 s[1] 被賦為空值。當 .NET 客戶端調用這個部署到 WebSphere 平台上的 Web 服務方法的時候,該 String 數組被序列化成:
清單 7. 來源於 WebSphere 的 Web 服務響應消息
<soapenv:Body>
<returnArrayWithNullResponse xmlns="http://array.test">
<returnArrayWithNullReturn>ABC</returnArrayWithNullReturn>
<returnArrayWithNullReturn xsi:nil="true"/>
<returnArrayWithNullReturn>XYZ</returnArrayWithNullReturn>
</returnEmptyStringResponse>
</soapenv:Body>
數組中的第二個元素是設置 xsi:nil="true"。這在 Java 中是非常有用的;Java 客戶端可以正確地將它反序列化成空的 String 值,該值是數組中的第二個元素。然而,.NET 客戶端將其反序列化成長度為 0 的字符串而不是空的字符串。長度為 0 和 空 在面向對象的編程語言中是完全不同的概念。
現在,考慮另一個部署在 WebSphere 上的 Web 服務方法,如清單 8 所示。
清單 8. 含有數組及其輸入輸出簽名的 Java 方法
public String[] processArray(String[] args) {
//do something to the input array and return it back to the client
return args;
}
這次,Web 服務方法將數組作為輸入,處理它,並將這個數組返回到客戶端。假設 .NET 客戶端發出含有空元素的數組,代碼如清單 9 所示。
清單 9. .NET 客戶端發出含有空元素的數組
TestArrayService proxy = new TestArrayService();
string[] s = new string[3];
s[0] = "abc";
s[1] = null;
s[2] = "xyz";
// Console.WriteLine("the length of the input array = " +
s.GetLength(0));
string[] ret = proxy.processArray(s);
// Console.WriteLine("the length of the output array = " +
ret.GetLength(0));
清單 10 展示了來源於 .NET 客戶端的 SOAP 請求。
清單 10. .NET 客戶端發出的 SOAP 請求
<soap:Body>
<processArray xmlns="http://array.test">
<args>abc</args>
<args>xyz</args>
</processArray>
</soap:Body>
.NET 客戶端發出的 SOAP 請求省略了空元素 s[1]。結果,返回的數組的長度不再與原始數組的長度一致。如果這個數組的長度或者元素的索引對於客戶端的邏輯來說是重要的,那麼客戶端將會失敗。
最佳的實例不是將含有空元素的數組傳遞到 Web 服務的客戶端及服務器上。
甚至原始的類型也能導致問題的出現
XML Schema 通過提供了大量的類型模型來減弱了互操作性。您可以構建 WSDL 消息及操作,因為 XML Schema 能夠識別 Web 服務所使用的特定的數據類型。XSD 提供了大量的類型以及簡單的結構。但是,每種編程語言都有一套自己的本地數據類型。本地數據類型與 XSD 數據類型之間的一對一的映射是不存在的。因此,在翻譯過程中可能丟失信息,或者接收端不可能生成某些本地數據類型的映射。
無符號的數值類型(如 xsd:unsignedInt、xsd:unsignedLong、xsd:unsignedShort 和 xsd:unsignedByte)是典型的例子。在 .NET 中,uint、ulong、ushort 和 ubyte 類型直接地映射到那些 xsd 類型中,但是 Java 語言沒有無符號的數值類型。考慮到互操作性,不要公布那些在 Web 服務方法中的數值類型。取而代之,您可以創建封裝器方法來公布並傳遞那些數值類型,如 xsd:string(使用 C# 中的 System.Convert.ToString)。
對於 xsd:decimal、xsd:double 和 xsd:float 類型,每個平台可能有不同的精度支持。結果,如果您沒有在整合之後測試 Web 服務那麼可能會降低精度。
無論數據類型是數值類型還是引用類型,信息傳遞的一方都可能出現問題。數值類型的對象位於棧中,但是引用類型的對象位於堆中。這意味著引用類型可能有空指針,但是數值類型不能有空值。如果 XSD 類型在一種語言中被映射成了數值類型,而在另一種語言中被映射成了引用類型,那麼這可能導致問題的出現。例如 xsd:dateTime 被映射成了 System.DateTime,這是 C# 中的數值類型。它也被映射成了 java.util.Calendar,這是 Java 中的引用類型。事實上,java.util.Date 和 java.util.Calendar 都是引用類型。在 Java 中,當引用類型沒有引用任何對象時將其賦空值,這是公共的操作。然而,如果 .NET Web 服務從 Java 客戶端接收到數值類型為空值的數據時,將拋出 System.FormatException。為了避免這個問題的出現,您可以定義復合類型來封裝數值類型,並將這個復合類型置為空來表示空引用。
結束語
在本文中,您可以看到由於使用某些數據類型而產生的一些互操作性的問題。為了在使用數據類型時能夠達到更好的互操作性,一般的規則是:
盡量多地使用簡單數據類型。完全避免使用那些異樣的復合類型,如 ArrayList、Tree,甚至公共的 Hashtable。
即使簡單的數組通常都具有非常好的同 Web 服務的交互性,注意數組中的內容,確保數組中的元素在每個平台上的含義都是相同的,並且避免發出含有空元素的數組。
注意每個平台都是如何實現一些本地原始類型的,如 float、double 和 dates 和 times。
在該系列文章中的下一部分,我將研究在 Web 服務互操作性上的命名空間所產生的影響。