SOA 探索,第 1 部分: 通過動態解耦來簡化 Web 服務調用-----執行穩定的 Web 服務調用的解決方案
學習如何使用應用動態代理模式(Dynamic Proxy Patterns)來進行動態解耦的 Web 服務適配器(Web Service Adapters)。通過適當地使用這種機制,您可以提供所需要的抽象級別,這樣有助于適當的面向服務的體系結構(Service-Oriented Architecture,SOA)的實現與服務重用。
理想的基于面向服務的體系結構系統設計的需求是在服務消費者與服務提供者之間進行解耦。解耦可采用多種形式,其范圍從靜態(在編譯時通過請求端存根來解耦)到全動態解耦(在所有服務調用的運行時完全封裝并建立解耦)。顯然,全動態解耦是一個嚴格的要求。如果一個服務被動態解耦,然后服務特性中的更改導致了服務實現中的修正,但是所有其它的系統元素,特別是調用機制,仍舊保持不變。這種設計的優點顯而易見。
對于 SOA 與 Web 服務的更強大功能與性能的日益增長的期望與需求經常導致顯著增長的軟件復雜性。今天,SOA 實現的實際情況是,盡管規劃了服務的功能模塊化,但是它們仍舊經常與獨立的中間件或通信協議(如基于 MOM 的 JMS、HTTP/SOAP 等等)耦合。在大多數組織中,當應用程序擴展功能時,開發者關注的,在某種程度上,是靜態解耦(通常是指綁定),并強制為每一個 Web 服務創建獨立的客戶端,而不是單獨的能夠同時訪問多個服務的客戶端。
靜態綁定的局限性是非常明顯的——代碼中的靜態綁定阻止了不同應用程序中貫穿整個企業的服務的重用。簡而言之,即使需要提取并封裝所用的不同中間件及協議來執行服務組件之間的交互,這些組件被包括在服務消費者與服務提供者的交互中,這樣的需求非常普遍,但是封裝了 Web 服務接口的體系結構還未得到廣泛應用。造成這種狀態的其中一個主要原因就在于,盡管動態綁定的幾種機制已被設計用來適應這種局限性,但是對動態綁定相關聯的實際技術依然存在很多的誤解。
![]() |
|
已經有許多關于 SOA 最重要的概念——服務接口(換句話說,調用功能)及在運行時如何定位并調用它們的文章。換句話說, Web 服務調用應該在后期綁定。需要指明的是 Web 服務調用已經被設計為傳統的 Remote Procedure Call(RPC)方法,該方法準許一個應用程序調用由第二個應用程序發布的功能。RPC 工作方式如下(如圖 1 所示):
- 被調用的應用程序的功能是以本地功能的形式展示給調用的應用程序的。
- 通過向調用的應用程序提供功能存根達到定位透明性。
- 功能存根,當它被調用時,訪問一個中間件層,該層向被調用的應用程序傳輸調用及其相關的數據。
圖 1. 基于 RPC 的 Web 服務調用

在 Web 服務中,存根代碼通常是自動產生的,并且是基于使用被調用的功能的接口描述,它通過 Web 服務描述語言(Web Services Description Language,WSDL)來表示,并創建了存根 shell。此外,存根通常是在應用程序開發的編碼階段產生,因為存根是主要是從應用程序代碼中直接調用,而且,結果是必須在編譯時解決的(換句話說,即所謂的前期綁定)。
大多數應用程序開發者,主要是 Java 開發者,認為使用這樣的存根是必要的,因為對他們來說,如何處理從調用服務返回的復雜數據類型是不清楚的。當然,來自被調用應用程序功能的數據必須同 SOAP 消息相分離,而且,當使用 Java 的時候,必須有一個相對應的(兼容的)實現序列化接口的類。顯然,同樣的存根產生工具被用于產生那些所需求的類。
本文的首要思想就是,一般來說,通常自動化是雙刃劍。一方面,自動化準許行業內 Web 服務的快速適應,其原因是,有了自動化工具,大多數應用程序開發者的行為不再發生在 WSDL 自身的級別上。另一方面,不幸的是,市場仍需要一種主要的工具來選擇需要使用的調用形式——顯式的存根(存根在編譯時解析)或隱式的存根(存根在運行時解析)。隱式的存根通常被當成 Web 服務的無存根調用。
大多數基于 Java 的 Web 服務工具提供了一些 Web 服務接口的運行時后期綁定功能。來自 webMethods 的 Glue 產品與 J2EE Web Services Developer 組件的 JAX-RPC 包就是兩個例子。然而,這些嘗試大多導致了軟件深受供應商特定邏輯的影響。而且,問題并不僅僅存在于供應商指定的代碼。在許多情況下,供應商專有的解決方案產生了實際的可管理性與可維護性的問題。
現在讓我們回顧提到的問題點,例如用于后期綁定的 webMethods Glue 方式。目前,Glue 是廣泛使用的工具。由于使用了一個代理用于 Web 服務的接口,Glue 看起來好象是無所不能。然而,僅僅通過自身使用一個代理并不能消除所有的問題。下面的 Java 代碼片斷描述了在它們的應用中的一個典型的負面情形:
清單 1. lateBindingGlue
|
您能從上面的例子中學到很多東西。首先,由 Glue 引入的代碼是一個兩層代碼。一層“消除”了 SOAP 分離的負面效應,通過使用 Java 異常處理程序和一個專門的組件——SOAP 攔截器來攔截這種特定的應用程序異常而實現。第二層將服務調用的結果作為完整的 XML 文檔來提交給調用應用程序。然后,XML 文檔通過使用一個諸如 DOM 結構的工具進行分離。顯然,這樣的方法不能“安全地”移植到其它 Web 服務工具中。但更重要的是,盡管攔截應用程序異常的技術廣為人知,但它真的適合于 Web 服務調用嗎?
一般而言,異常是 Java 語言極為有用的特性,特別是,這意味著一種檢測錯誤位置、無計劃的編程操作(如使用空指針)的簡單方式。使用這種技術作為一個計劃的控制操作,而不是寫一個 if-then 邏輯段來測試一個指針是否真正為空,使代碼的可管理性與可維護性顯著地復雜化。顯然,SOAP 分離并不同于空指針。您不能通過一個簡單的 if-then 代碼測試出來。不過,它們的負面影響是相同的——拋出異常并捕獲它,這是一項代價非常高昂的技術,特別是如果您需要獲得每秒數千次的 Web 服務調用的話。換句話說,運行時異常應該專門針對意外情況作為“防御線”而保留,以應對軟件的錯誤。
我進一步指出為什么分離 SOAP 的問題不能通過一個 Java Exception API 進行處理的更多原因。但是,讓我們先來看看引用的范例所顯現的另外一個問題。由于調用操作需要直接分析 XML,它不得不訪問響應信息的 XSD 定義,而且必須依次成為提供的 WSDL 文件的一部分(且被存根產生工具所使用)。這里的最終結果就是甚至一些后期綁定的形式也存在于引例之中;在 Web 服務伙伴之間的緊耦合依舊存在。換句話說,Web 服務的客戶端必須訪問 WSDL 文件,并且在大多數情況下,它必須是一個完整(而不是部分)的 WSDL 文件。
還存在一個與使用 JAX-RPC API 相關的問題。記住這是非常必要的:在 Java 語言中,由于在 XML schema 中的數據類型不能直接準確地映射到 Java 語言的數據類型當中,所以在調用的應用程序端 WSDL 中產生的服務終端接口將與 JAX-RPC 編譯器在調用端產生的 WSDL 的形式不同。而且,生成的服務接口也將依賴于 SOAP 采用的編碼體制。例如,使用文檔文字編碼利用了每個方法類的封裝版本,并造成了額外的可管理性與可維護性的問題。
![]() |
|
根據到目前為止的講述,您容易想到使用任何種類的動態調用技術(如后期綁定的動態代理),不幸的是,許多開發者都是這樣做的。關于 Web 服務其中一個最常見的誤解就是,使用動態調用取代靜態存根的好處并沒有那么大,而且最好是堅持用生成的存根作為 Web 服務調用的主要方法。
最后,我們的討論轉到第二個,而且是最重要的原則——它不是真正的編程技術,它確保不依賴服務編程構件(如 WSDL),但是取而代之的是,它的價值體現在高級設計中。這對于獲得 SOA 的真正利益是必要的,因為隨著服務的發展,服務消費者的代碼將完全不受影響。無論如何,基于 SOA 的應用程序必須注重于提供彈性體系結構,且更少的關注于公共通信協議(比如 SOAP)。服務與應用之間不應該具有嚴格的界限。一個終端用戶的應用程序可以被看作是其它終端用戶的服務。整個企業 IT 環境應該被設計成高度模塊化,允許開發者能夠挑選適合他們需要的服務與應用組合。
在良好的 SOA 設計中,Web 服務消費者的應用邏輯能夠使用兩個基礎體系結構原則來從服務構件完全解耦:
- “保持可適應性”——創建一種方法來支持多接口繼承(理論上,這將不受限制),在開發有限數量的具體實現(在面向對象的設計當中,經常將匿名的內部類作為對象適配器使用,考慮上下文,允許工作行為的定制,實際上是嵌入在子類之中)。
- “使用所謂的好萊塢原則:不要調用我們,我們將調用您”——Web 服務調用模型應該只通過動態代理框架來指定發現,并假定客戶端事先對服務一無所知;因此,Web 服務提供方應該能夠通過將存根類信息聲明到 XML 文檔,從而在運行時宣傳他們的服務。那實際上就以一種開放且可執行的方式,提供了發現之外的另一種調用方法(換句話說,優于 UDDI)。
最終設計目標是,為 Web 服務消費者應用提供一組類,這些類根據上面引述的原則組成了可重用適配器層。這種適配器層封裝了使用存根及其生成的類的代碼。適配器的公共 API 并不公開任何存根類;而是將其映射到 Web 服務消費者應用可以理解的類。
為了得到最好的適配器靈活性與可擴展性,適配器的總體類結構均通過使用下列設計模式的組合來構建:
- 動態代理是一種結構模式,它定義了一組類與方法調用,這些類和調用被分派給調用句柄(在動態代理類生成的時候指定)。使用動態代理是 Java 編程中模擬多實現繼承的關鍵。通過動態代理,定制的 InvocationHandler 能夠由一組表示合成子類的超類來構建;該子類的接口將是這些超類實現的接口的聯合。
- 適配器是一種結構模式,它通過對實現的抽象進行解耦來影響類層次(比如繼承層次)的創建,因此兩者可以單獨改變。這種允許在運行時選擇實現的解耦避免了抽象與其實現間的永久綁定。作為 Web 服務客戶端的調用應用的類僅處理抽象。
- 服務配置器是一種行為模式,它使您可以改變適配器超時性能,并添加或去除附加功能,這樣就改變了調用框架的規范。例如,如果一個 Web 服務提供方引入了一個新的協議(如在 RMI 上的 SOAP),那么它只需要“宣傳”新的傳輸性能。因此,適配器演示了這種新服務能力與這種調用功能的任何實現。使用一些元數據表示作為結果,必須具備一種方法能夠引用提供了獨立于特定規范與實現的功能類的接口。
- 工廠方法(Factory Method )是一種結構模式,它使一個類延遲實例化到子類。在 Web 服務調用的情況下,本地與遠程實現類都必須是子類,以實現它們的特定于服務的實現。需要訪問服務的調用應用將得到這個工廠的一個句柄,并給服務一個調用。因此,根據從服務配置器得來的信息,工廠封裝了訪問服務必須使用的實現知識。
- 裝飾(Decorator)模式是一種結構模式,它定義了緩存、發布與交換服務聲明的封裝器,用于連接合適服務與可重用的代理類。 通過使用裝飾模式,實現執行調用的代碼與提供緩存的代碼相分離,將非常簡單。
![]() |
|
圖 2 演示了使用上面引用的設計原則而設計的“好萊塢”類型適配器的概念模型。 圖 3 展示了使用適配器時的基本交互的序列圖。
圖 2. Web 服務適配器的概念模型

圖 3. 包含 Web 服務適配器的序列圖

創建一個使用適配器的服務,包括創建一個使用服務描述符(代表 SOAP 服務)的服務類。這個描述符被包括在服務類成員之中。服務類被作為 SOAP 來部署,例如,使用來自 Open Source Foundation 的 Axis。
服務描述符包括:
- 類名:包含服務實現的全限定類名的元素。
- 名稱:包含服務名的元素。
- 版本:包含服務版本名的元素。
- 創建者:包含服務創建者名稱的元素。
- AssertURI:包含指向一個 XML 文件的統一資源標識符(URI)的元素。其中,XML 文件包括服務聲明(規范)。
- 描述:包含服務描述的元素。
現在,有一個關于 Axis 的耦合注意事項。使用 Axis 作為 SOAP 引擎非常有助于實現 Web 服務的松耦合,理由如下:
- 因為 Axis 定義了消息處理節點,該節點能夠被封裝以供服務請求者(客戶端)與服務提供者(服務器)使用,適配器能夠使用部署描述符作為一個 Web 服務部署描述符(WSDD),應用一個消息處理節點來部署 SOAP 服務。
- Axis 中的服務通常是 SOAP 服務的實例,它包含請求與響應鏈,但是必須包含實際服務類的提供方。結果是,通過將消息上下文傳遞給消息處理節點來完成 SOAP 服務的處理。
- Axis 客戶端處理能夠通過使用服務描述符和 XML 聲明文件創建包含服務細節的調用工廠實例,從而構建調用對象。調用對象的屬性被設定為使用相關的目標服務。然后,調用對象就通過調用 Service.createCall 工廠方法來創建。一旦建立起調用,
Call.SetOperation
就通過被調用的方法名稱來指定。然后,Call.invoke
通過相關的請求消息而被調用,它驅動AxisClient.invoke
,處理結果消息上下文,并向客戶端返回響應消息。
在適配器的設計中,應該對性能進行特殊考慮。使用動態代理類具有性能含義。訪問目標的直接方法快于訪問代理類的方法。然而,這不應是在穩健體系結構與性能之間進行選擇。這就是為什么目前適配器實現緩存封裝器的原因。通過應用緩存,使用靜態存根與基于適配器的解決方案之間的差異相對較小。根據如何實現緩存,必須提到可能的解決方案之一——記憶(Memoization)。 記憶是一種廣泛使用的技術,它在 Lisp、Python 與 Perl 這樣的功能編程語言中使用,給功能賦予預先計算的值。記憶一個功能將為功能添加一個透明的緩存封裝,因此已經得到的值將從緩存中返回而不是每次都重建。記憶可以顯著提高動態代理調用的性能。
總結我們關于此處描述的適配器的討論,允許支持本地和遠程服務實現的設計是非常重要的。服務類對適配器來說將是本地的,而遠程 Web 服務則可以相互替換,因為服務類和代理類使用同樣的接口來訪問遠程 Web 服務。本地服務類將實現與 Web 服務器遠程實現相類似的方法 getFunction()
來返回功能的結果。下面的 Java 代碼片斷進一步說明了這一點:
清單 2. 本地服務類及其接口
|
實現了 IService 的代理類采用了方法 getFunction()
,但并未考慮該方法需要訪問遠程 Web 服務的代碼。這些代碼代表了需要用來訪問部署在適配器內的任意 Web 服務的代理代碼。
清單 3. 遠程服務
|
![]() |
|
顯然,使用 Web 服務的靜態存根與前期綁定是 Web 服務調用的最簡單的形式。但是簡單化也帶來了明顯的缺點。與緊耦合方法不同,使用本文提出的適配器方法將給您留下可高度重用和可配置的 Web 服務代碼。由于 Web 服務 調用全部通過部署了服務描述符的公共適配器來引導,因此您能夠動態地決定調用什么服務——在部署代碼時或在運行時。
同樣,那些可能尋找商業產品作為 Web 服務松耦合調用定制解決方案的組織應該研究企業服務總線(Enterprise Service Bus,ESB)技術(參見參考資料),該技術提供了類似于上面所引述的功能的選項來連接企業內部及跨企業的可作為 Web 服務的應用程序,并具備一套功能來管理并監視已連接應用之間的交互。
posted on 2006-04-17 03:56 wsdfsdf 閱讀(264) 評論(0) 編輯 收藏 引用 所屬分類: 技術文章