Web服務和SOA(五)

基於SOAP協議的Web服務風格之比較<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 前面我們已經看到,SOAP可以在後臺替我們完成那些比較困難的工作。但我們並沒有看到服務器端和客戶端交互的XML文檔,實際上,我們可以利用一些TCP/IP監測工具,比如ApacheTCPMon工具來查看SOAP中傳輸的XML文檔,其結果如代碼清單16所示:

代碼清單16 – SOAP中的XML請求文檔

<?xml version="1.0" ?>

<soapenv:Envelope

xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:ns1="http://item.service.soap.soajava.packt.com/">

<soapenv:Body>

<ns1:insert>

<arg0>

<code>XY</code>

<description>xy desc</description>

<id>26</id>

</arg0>

</ns1:insert>

</soapenv:Body>

</soapenv:Envelope>

 

代碼清單17 – SOAP中的XML應答文檔

<?xml version="1.0" ?>

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">

<S:Body>

<ns2:insertResponse

xmlns:ns2="http://item.service.soap.soajava.packt.com/">

<return>

<retCode>OK</retCode>

<retMessage>Item was inserted successfully</retMessage>

</return>

</ns2:insertResponse>

</S:Body>

</S:Envelope>

您可以看出,SOAPXML消息以Envelope元素開始,包含一個必須的Body元素,但SOAP消息中的header元素是可選的。Body元素中包含的內容就是要傳輸消息的有效負載。然而,我們在使用SOAP時會有多種選擇,就前面的例子而言,我們爲了簡明起見,只採用了SOAP的缺省設置。您有可能聽說過SOAP的綁定風格(Binding style,其值爲RPCDocument風格)、使用(use,其值爲EncodedLiteral)或參數風格(parameter style,其值爲BareWrapped)。在本節中,我們將探討一下這些概念,重點介紹一下SOAP的綁定風格。

在解釋這些概念之前,我們先花點時間解釋一下WS-I這個概念,它是Web Service-Interoperability(Web服務的可互操作性)的縮寫,它代表了一整套的標準,這些標準組合起來可以讓我們在異種環境中(比如.NetJava)進行數據交換。綁定風格、使用和參數風格可以任意組合,但只有以下的組合和WS-I兼容,所以它們是我們討論的重點:

(1)    RPC/Literal

(2)    Document/Literal(bareunwrapped)

(3)    Document/Literal wrapped

WS-I規定,use屬性值不能等於”encoded(已編碼的)”。如果use屬性值等於encoded,數據將按照SOAP編碼規範進行編碼,但要驗證經過SOAP編碼的消息是否違反WSDL描述將非常困難,而驗證工作又是可互操作性的基礎,所以WS-I不對數據進行編碼,它只傳輸literal(不經編碼的)的數據。

SOAP服務的可選架構一 : RPC/Literal

SOAP服務的第一個可選架構爲RPC/Literal,它決定我們是否要在Web服務中使用RPC還是要使用Document作爲SOAP的綁定風格。RPC(遠程過程調用)是遠程調用的一種通用機制,駐留在一臺計算機(或虛擬機)上的程序可被運行在遠程機器(或虛擬機)上的程序調用,這種方法已經使用了幾十年。RPC可由多種技術實現,其中比較流行的有CORBADCOMRMI。實現技術雖然不同,但RPC調用都具有以下特點:

(1)    需要有遠程地址

(2)    需要調用的方法(或操作)

(3)    需要一系列參數

(4)    需要同步應答

請注意,除了上面的第一個特點,RPC調用與本機調用幾乎相同。那麼,古老的RPC方法和SOAP服務又有什麼關係呢?其實它們之間關係非同一般,SOAP在其早期定義(尚未發佈之前)中,其設計只支持RPC。從某種意義上說,當時的SOAP是當時各種分佈式編程技術的集大成者,它對這些技術進行了標準化。

我們可以對前面的Web服務稍加修改,加些註解(annotation),我們就可以將它轉化爲RPC風格(JAX-WS的缺省風格爲Document而非RPC),其實現代碼如下:

代碼清單18 – SOAPRPC風格

@WebService

@SOAPBinding(style=SOAPBinding.Style.RPC)

public class ItemWs {

@WebMethod

public Outcome insert(@WebParam(name="itemParam") Item item,

@WebParam(name="categoryParam") String category)

{

//Insert item ...

您會看到,我們在方法中加了另一個參數category,這樣我們就可以把某個商品插入到某一指定的商品集中,並且我們使用了@WebParam註解給方法中的參數命名。

現在我們來發布這個服務,我們只需要運行帶有EndPoint.publish的那個主類即可,而不需要運行wsgen來生成服務器端的類。然後,請使用wsimport工具根據發佈的WSDL生成客戶端類,並導入到客戶端項目中,這樣,我們就可以通過下面的方式在客戶端調用該RPC風格的SOAP服務。

Outcome outcome = itemWs.insert(item1, "A");

如果我們監測XML請求文檔,我們將會看到如下結構:

代碼清單19 –RPC風格的SOAP請求文檔

<soapenv:Body>

<ans:insert xmlns:ans="http:// ... ">

<itemParam>

<code>XY</code>

<description>xy desc</description>

<id>26</id>

</itemParam>

<categoryParam>A</categoryParam>

</ans:insert>

</soapenv:Body>

我們可以從上面的請求文檔中看到RPC的典型風格,即方法名加上一系列參數。其相應的WSDL(您可以使用TCP監測工具查看)文檔如下所示:

代碼清單20 –RPC風格的WSDL文檔

<types>

<xsd:schema>

<xsd:import schemaLocation="http://127.0.0.1:8002/SoaBookSOAP_RPC_server/itemWs?xsd=1"

namespace="http://item.service.soap.soajava.packt.com/"></xsd:import>

</xsd:schema>

</types>

<message name="insert">

<part name="itemParam" type="tns:item"></part>

<part name="categoryParam" type="xsd:string"></part>

</message>

<message name="insertResponse">

<part name="return" type="tns:outcome"></part>

</message>

<portType name="ItemWs">

<operation name="insert" parameterOrder="itemParam categoryParam">

<input message="tns:insert"></input>

<output message="tns:insertResponse"></output>

</operation>

</portType>

<binding name="ItemWsPortBinding" type="tns:ItemWs">

<soap:binding style="rpc"

transport="http://schemas.xmlsoap.org/soap/http">

</soap:binding>

<operation name="insert">

<soap:operation soapAction=""></soap:operation>

<input>

<soap:body use="literal" namespace=

"http://item.service.soap.soajava.packt.com/">

</soap:body>

</input>

<output>

<soap:body use="literal" namespace=

"http://item.service.soap.soajava.packt.com/">

</soap:body>

</output>

</operation>

</binding>

<service name="ItemWsService">

<port name="ItemWsPort" binding="tns:ItemWsPortBinding">

<soap:address location=

"http://127.0.0.1:8002/SoaBookSOAP_RPC_server/itemWs">

</soap:address>

</port>

</service>

</definitions>

上面WSDLXML Schema文檔可以通過下面的URL地址來訪問:

http://127.0.0.1:8001/SoaBookSOAP_RPC_server/itemWs?xsd=1

WSDLXML Schema文檔內容如下所示:

代碼清單21 –RPC風格WSDL文檔的XML Schema(XSD)文檔

<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:tns="http://item.service.soap.soajava.packt.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://item.service.soap.soajava.packt.com/" version="1.0">

<xs:element name="Item" type="tns:item"></xs:element>

<xs:element name="Outcome" type="tns:outcome"></xs:element>

<xs:complexType name="item">

<xs:sequence>

<xs:element name="code" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="description" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="id" type="xs:int"></xs:element>

</xs:sequence>

</xs:complexType>

<xs:complexType name="outcome">

<xs:sequence>

<xs:element name="retCode" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="retMessage" type="xs:string"

minOccurs="0"></xs:element>

</xs:sequence>

</xs:complexType>

</xs:schema>

這裏,我們需要注意的是,上面的XSD只定義了複雜類型元素,如本例中的ItemOutcome類型,但它沒有辦法提供有效的信息來驗證簡單數據類型的參數,也不能驗證SOAP消息的其它部分。因此,採用RPC架構的主要問題是,如何才能驗證要交換消息的XML文檔符合XSD規範?

接下來我們看看Document風格的架構能否客服這一缺點。

SOAP服務的可選架構二 : Document/Literal

這種SOAP風格的實現也稱之爲”bare””unwrapped”(未組裝的)Document風格,爲什麼叫未組裝的Document,我們馬上會解釋其中的含義。但現在請您注意,JAX-WS中的參數類型的缺省值是wrapped(已組裝的),因此,爲了使用bare類型的document風格,我們需要在源代碼中顯式聲明:

代碼清單22 –Document風格SOAP服務實現

@SOAPBinding(style=SOAPBinding.Style.DOCUMENT,

parameterStyle=SOAPBinding.ParameterStyle.BARE)

public class ItemWs {

@WebMethod

public Outcome insert(@WebParam(name="itemParam") Item item,

@WebParam(name="categoryParam") String category) {

...

如果您現在按正常的步驟在客戶端調用上面的服務,您會在導入由WSDL產生的客戶端類時發現下面的錯誤:

error: operation "insert": more than one part bound to body

這個錯誤有助於我們理解RPCDocument風格的SOAP實現方法之間的區別,我們已經瞭解,WS-I只允許SOAP消息體中只能包含一個XML根元素,這是爲什麼呢?

Document風格是SOAP實現的一種新方法,它不同於以往的方法。其表現在,服務要求的輸入是一個文檔,而不是一個帶方法名及其相應參數的一個請求。這意味着在Document風格中,我們應該傳入一個對象,這個對象將是Web服務的唯一輸入。Document中會包含服務調用的所有有用信息,但不是方法名及其相應參數值的組合。這就是WS-I爲什麼只允許Document只能包含一個根元素的原因。

因此,爲了調用上面的Document風格的服務,把商品插入到商品目錄中,並保持和WS-I兼容,我們應重構服務的源代碼。我們應該新建一個對新,比如ItemInsertRequest對象,這個對象將包裝所有需要的信息(商品及其目錄)。這就是爲什麼這種風格又稱之爲”bare””unwrapped”Document風格的由來,它沒有對XML消息的組成部分進行包裝,我們必須手動的顯式包裝它們。

代碼清單23 –Document風格的SOAP服務對請求進行組裝

@XmlRootElement(name = "ItemInsertRequest")

public class ItemInsertRequest {

private Item item;

private String category;

...

而且,爲了使Web服務只需要一個參數,我們還需要對Web服務的實現進行重構,其實現代碼如下:

代碼清單24 –Document風格的SOAP服務實現代碼(使用了上面定義的封裝類)

@WebMethod

public Outcome insert(@WebParam(name="itemInsertRequestParam")

ItemInsertRequest itemInsertRequest) {

...

此時,但客戶端使用wsimport工具生成客戶端需要的類時,就不會產生上面的錯誤,客戶端最終調用服務的代碼如下:

代碼清單25 –Document風格的客戶端調用代碼

ItemInsertRequest req = new ItemInsertRequest();

req.setItem(item1);

req.setCategory("A");

Outcome outcome = itemWs.insert(req);

 

上面的代碼執行後,它會想服務器發出SOAP請求,其請求消息體的內容如下:

代碼清單26 –Document風格XML請求

<ns1:itemInsertRequestParam>

<category>A</category>

<item>

<code>XY</code>

<description>xy desc</description>

<id>26</id>

</item>

</ns1:itemInsertRequestParam>

請注意,上面Document風格的XML請求在結構上雖然和RPC風格相似(請參展代碼清單19),但我們現在處理的是XML文檔(Document),而不是對遠程方法的調用。在Document文檔中,商品及其目錄只是文檔的屬性;但在RPC風格的方法調用中,它們是作爲方法的參數傳入的。

代碼清單27–Document風格WSDL

<message name="insert">

<part element="tns:itemInsertRequestParam"

name="itemInsertRequestParam"></part>

</message>

...

<binding name="ItemWsPortBinding" type="tns:ItemWs">

<soap:binding style="document"

這時,上面WSDL附屬的XSD就可以用來對整個WSDL文檔進行驗證。

代碼清單28–Document風格WSDL附屬的XSD文檔

<xs:element name="Item" type="tns:item"></xs:element>

<xs:element name="ItemInsertRequest"

type="tns:itemInsertRequest"></xs:element>

<xs:element name="Outcome" type="tns:outcome"></xs:element>

<xs:element nillable="true" name="insertResponse"

type="tns:outcome"></xs:element>

<xs:element nillable="true" name="itemInsertRequestParam"

type="tns:itemInsertRequest"></xs:element>

<xs:complexType name="itemInsertRequest">

<xs:sequence>

<xs:element name="category" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="item" type="tns:item"

minOccurs="0"></xs:element>

</xs:sequence>

</xs:complexType>

<xs:complexType name="item">

<xs:sequence>

<xs:element name="code" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="description" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="id" type="xs:int"></xs:element>

</xs:sequence>

</xs:complexType>

Document/Literal風格的SOAP服務的主要優勢在於,它能對整個XML交換消息進行驗證。

但是,使用這種方法,我們會丟掉一些信息,如我們在SOAP消息中沒有發現操作的名稱;在某些情況下,比如在使用SMTP等異步TCP/IP協議來傳輸消息時,這可能會有所不足。因爲此時消息的派發將變得非常困難,有時甚至不可能做到。

另外,這種方法的另一個缺點是,對那些已經開發完成的應用,我們得考慮花力氣對它們進行改造,即對服務器端和客戶端的代碼進行重構。

SOAP服務的可選架構三 : Document/Literal Wrapped

Document/Literal Wrapped風格是JAX-WS的缺省風格,在我們第一個SOAP示例中,我們用的就是這種風格(請參考程序清單14)。因此,下面的註解其實是沒用的。

代碼清單29–Document  Wrapped風格的服務實現

@SOAPBinding(style=SOAPBinding.Style.DOCUMENT,

parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)

現在,我們在Document bare風格代碼的基礎上,初始化Web服務的結構。

代碼清單30–Document  Wrapped風格的服務實現

@WebMethod

public Outcome insert(@WebParam(name="itemParam") Item item,

@WebParam(name="categoryParam") String category) {

要在客戶端調用該Web服務,按照正常的步驟進行即可(此時需要調用wsgen工具生成服務器端需要的類)

現在,即使Web方法中帶有類似於RPC風格的多個參數,在服務的調用過程中也不會產生錯誤。這是如何做到的呢?原來,它只是對方法名和參數進行簡單的封裝,將方法名和參數當成一個新的對象,新對象名和Web方法名正好一致。當然,這個封裝過程是自動完成的。此時,SOAP消息體的內容如下:

代碼清單31–Document  Wrapped風格的XML請求

<ns1:insert>

<itemParam>

<code>XY</code>

<description>xy desc</description>

<id>26</id>

</itemParam>

<categoryParam>A</categoryParam>

</ns1:insert>

總之,Document/literal wrapped風格集成DocumentRPC方法的優勢,它具有以下優點:

(1)    SOAP消息可以用XML Schema進行驗證

(2)    SOAP消息體只有一個子元素,因此它和WS-I兼容

(3)    如果Web方法帶有多個參數,不需要修改代碼就可發佈這種類型的服務

(4)    操作的方法名包含在SOAP消息中

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章