引言
人們為什麼喜歡使用 xsd:any 呢?我聽人們談起過很多原因:為了實現松散耦合、為了啟用版本管理、為了獲得完全靈活的多態性。而所有這些理由最終都歸結為不需對 API 進行更改。這是一個很不錯的理由,但在實踐中,使用 xsd:any 既不恰當,也不實際。如果所處的是動態環境,僅處理 XML,則 xsd:any 可能非常合適(也的確如此)。但大多數人並不喜歡動態訪問 XML(即 SAX 或 DOM 或 SAAJ 編程模型)的復雜性,而且,即使他們喜歡這種復雜性,動態性也會受到局限。而 XML 很少存在於“真空環境”中。XML 幾乎總是映射到別的對象或與其他對象進行交互。例如,XML 模式可能作為 Web 服務的一部分而存在,而這意味著將可能處理語言綁定問題。因此,應當對語言綁定將 xsd:any 映射到給定語言的方式加以注意。在本文中,我將重點討論 JAX-RPC Java™ 語言綁定。
使用 xsd:any 的 Web 服務示例
讓我們看一下清單 1 中的 WSDL 文件示例。這是一個簡單的寵物醫院 Web 服務。它定義了一個名為 registerPet 的操作,其輸入信息包含一個 xsd:any。此 WSDL 還定義了兩個寵物類型:cat 和 fish。
清單 1. 使用 xsd:any 的獸醫服務 WSDL
<?XML version="1.0" encoding="UTF-8"?>
<definitions
targetNamespace="urn:any"
xmlns:soap="http://schemas.XMLsoap.org/wsdl/soap/"
XMLns:tns="urn:any"
xmlns:wsdl="http://schemas.XMLsoap.org/wsdl/"
xmlns="http://schemas.XMLsoap.org/wsdl/">
<types>
<schema
targetNamespace="urn:any"
XMLns:tns="urn:any"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.w3.org/2001/XMLSchema">
<complexType name="Cat">
<sequence>
<element name="name" type="xsd:string"/>
<element name="breed" type="xsd:string"/>
</sequence>
</complexType>
<complexType name="Fish">
<sequence>
<element name="name" type="xsd:string"/>
<element name="saltWater" type="xsd:boolean"/>
</sequence>
</complexType>
<element name="registerPet">
<complexType>
<sequence>
<any namespace="##any"/>
</sequence>
</complexType>
</element>
<element name="registerPetResponse">
<complexType>
<sequence/>
</complexType>
</element>
</schema>
</types>
<message name="registerPetRequest">
<part name="registerPetRequest" element="tns:registerPet"/>
</message>
<message name="registerPetResponse">
<part name="registerPetResponse" element="tns:registerPetResponse"/>
</message>
<portType name="Vet">
<Operation name="registerPet">
<input message="tns:registerPetRequest"/>
<output message="tns:registerPetResponse"/>
</Operation>
</portType>
<binding name="VetSOAP" type="tns:Vet">
<soap:binding style="document"
transport="http://schemas.XMLsoap.org/soap/http"/>
<Operation name="registerPet">
<soap:Operation/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</Operation>
</binding>
<service name="VetService">
<port name="VetSOAP" binding="tns:VetSOAP">
<soap:address location="http://localhost:9080/VetService/services/Vet"/>
</port>
</service>
</definitions>
registerPet 的 xsd:any 參數可以為以下二者之一:
Cat 或 Fish 的實例
在其他位置或別的時候定義的其他寵物類型的實例。
xsd:any 背離了 WSDL 的用途
請注意,上面列出的兩個參數並未在 WSDL 中定義。我自己確定其定義。WSDL 即 Web 服務描述語言 (Web Services Description Language)。其主要的用途就是盡可能完整地描述服務的接口。當使用 xsd:any 時,您就背離了 WSDL 的此用途。
第一個項目符號聲明該參數將為 cat 或 fish。但 WSDL 並不告訴您這一點。它所標明的是該參數可以為任何事物。考慮到這是一個獸醫服務,則可能不會處理訂單或保險索賠或稅收報表——但您可能恰恰會這樣猜想;WSDL 並沒有告訴您這些信息。
處理此偽多態性的一個更好方法是定義真正的多態性。定義一個基類型,稱為 Pet,然後重新編寫 Cat 和 Fish 來對 Pet 進行擴展。
第二個項目符號聲明其他類型將在其他地方或別的時候進行定義。這表明這個 WSDL 並不是此服務的完整接口。這可能會讓人覺得可以更改服務並保持 API 不變,但由於此 WSDL 不是完整的 API,這樣的說法就不成立了。對於使用此 WSDL 的客戶機,要成功地與此服務進行通信,僅這個 WSDL 是不夠的。客戶是否可以將短吻鳄 (alligator) 送來接受服務?只通過查看 WSDL,您不會知道答案(我認識的可以給短吻鳄看病的獸醫也不多!)。您需要一些更多的帶外定義。
JAX-RPC 與 xsd:any 之間的關系不是特別友好
最後,為了使用此服務,您必須將此 WSDL 映射到某個語言綁定。我將討論一下 JAX-RPC 規范方面的情況。根據 JAX-RPC,xsd:any 映射到 javax.xml.soap.SOAPElement,而後者是在 SAAJ 規范中定義的類型。SAAJ 是與 DOM 類似的 API,用於訪問 SOAP 消息中的 XML 實例。SAAJ 是一種用於訪問 XML 實例數據的非常動態的低級別方法,不是特別友好。請參見清單 2,以查看清單 1 中的 WSDL 的 Java 接口。
清單 2. 使用 xsd:any 的 WSDL 的 Java 接口
package any;
public interface Vet extends Java.rmi.Remote {
public void registerPet(Javax.XML.soap.SOAPElement any)
throws Java.rmi.RemoteException;
}
對於動態程序員而言,這可能是個非常好的映射,但我們其他人卻寧願使用生成的 Cat 和 Fish 類,也不願使用 SOAPElement。如果可以直接將一個 Cat 或 Fish 放入 SOAPElement 中,這樣也挺好,但沒有特定於 SAAJ 的方式來進行此操作。JAX-RPC 規定 xsd:any 映射到 SOAPElement,但仍然未能提供用於在類實例和 SOAPElement 之間進行轉換的方法。
如果您習慣使用 DOM、SAX 或 SAAJ,則 xsd:any 對您而言就不是問題。不過您需要考慮一下您的 WDSL 的使用者的情況。僅由於您習慣這種級別的編程,您的 WSDL 的其他使用者是否也對此很熟悉呢?
關於 xsd:anyType 的一些思考
xsd:any 的一個替代方法是 xsd:anyType(請參閱清單 3 中的 WSDL——與清單 1 的不同之處在於使用粗體突出顯示——和清單 4 中的 Java 接口)。xsd:anyType 的缺點在於 JAX-RPC 未為其定義映射。因此,即使有供應商映射此 XML 類型,您仍然無法編寫使用該類型的可移植代碼。雖然這樣說,此類型仍然有一個優勢,即一些供應商(如 IBM)將其映射到了 java.lang.Object 之類的有用類型。對於 WebSphere Application Server 的 SOAP 引擎,如果方法接受 java.lang.Object 類型的參數,則可以將任何 Java 對象發送到該方法,而且,只要該對象是符合 XML 標准的對象,就可以將其序列化到 SOAP 消息中。另一方面,如果接收端 SOAP 引擎知道如何將 XML 實例反序列化為 Java 對象,它就將進行此操作。如果引擎不知道如何反序列化該對象,接收方將獲得一個 SOAPElement,和使用 xsd:any 時一樣。
請記住,盡管 xsd:anyType 看起來比 xsd:any 更好用,但缺乏 JAX-RPC 映射支持。只有在以下情況它才具有優勢:
您不擔心代碼是不可移植的。
服務實現和該服務對應的客戶機實現的基礎平台具有針對 xsd:anyType 的某種映射。
清單 3. 使用 xsd:anyType 的獸醫服務 WSDL
<?XML version="1.0" encoding="UTF-8"?>
<definitions
targetNamespace="urn:any"
xmlns:soap="http://schemas.XMLsoap.org/wsdl/soap/"
XMLns:tns="urn:any"
xmlns:wsdl="http://schemas.XMLsoap.org/wsdl/"
xmlns="http://schemas.XMLsoap.org/wsdl/">
<types>
<schema
targetNamespace="urn:any"
XMLns:tns="urn:any"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.w3.org/2001/XMLSchema">
<complexType name="Cat">
<sequence>
<element name="name" type="xsd:string"/>
<element name="breed" type="xsd:string"/>
</sequence>
</complexType>
<complexType name="Fish">
<sequence>
<element name="name" type="xsd:string"/>
<element name="saltWater" type="xsd:boolean"/>
</sequence>
</complexType>
<element name="registerPet">
<complexType>
<sequence>
<element name="pet" type="xsd:anyType"/>
</sequence>
</complexType>
</element>
<element name="registerPetResponse">
<complexType>
<sequence/>
</complexType>
</element>
</schema>
</types>
<message name="registerPetRequest">
<part name="registerPetRequest" element="tns:registerPet"/>
</message>
<message name="registerPetResponse">
<part name="registerPetResponse" element="tns:registerPetResponse"/>
</message>
<portType name="Vet">
<Operation name="registerPet">
<input message="tns:registerPetRequest"/>
<output message="tns:registerPetResponse"/>
</Operation>
</portType>
<binding name="VetSOAP" type="tns:Vet">
<soap:binding style="document"
transport="http://schemas.XMLsoap.org/soap/http"/>
<Operation name="registerPet">
<soap:Operation/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</Operation>
</binding>
<service name="VetService">
<port name="VetSOAP" binding="tns:VetSOAP">
<soap:address location="http://localhost:9080/VetService/services/Vet"/>
</port>
</service>
</definitions>
清單 4. 使用 xsd:anyType 的 WSDL 的 Java 接口
package any;
public interface Vet extends Java.rmi.Remote {
public void registerPet(Java.lang.Object pet)
throws Java.rmi.RemoteException;
}
總結
盡管在 Web 服務的 XML 模式中使用 xsd:any 似乎是個不錯的主意,但這樣做存在一些缺陷:您將背離 WSDL 的用途,而 xsd:any 的語言映射可能不便於使用。雖然我並不會說使用 xsd:any 是一種不好的做法,但我希望這個提示能在出現問題前讓您對即將出現的問題有更好的認識。