1.引言
SOA是一種構造分布式系統的方法,它將業務應用功能以服務的形式提供出來,以便更好的復用、組裝和與外部系統集成,從而降低開發成本,提高開發效率。SOA的目標是為企業構建一個靈活,可擴展的IT基礎架構來更好地支持隨需應變的商務應用。
隨著SOA技術和產品的不斷成熟,現在越來越多的用戶開始了解并認同SOA的理念,但對SOA項目的實施還缺乏信心。其主要原因是:SOA應用開發還相對比較復雜。
一年多來,本文作者所在的部門已經從事了許多國內外的SOA項目的實施和支持工作,積累了許多SOA應用開發經驗。我們希望能夠通過一系列的文章與讀者分享這些想法,幫助您更好地構建SOA應用。
本文將從Web Service調用入手,在解決一系列具體問題的過程中,使用IoC (Inversion of Control) 和AOP (Aspect- Oriented Programming) 等方法重構Web Service的訪問代碼,使得業務邏輯與Web Service訪問解耦,為您提供一個更加靈活和易于擴展的訪問模式。
Spring是一個流行的輕量級容器,對IoC和AOP提供了良好的支持。本文為您提供了一個基于Spring的實現供您下載學習。示例代碼工程使用Eclipse3.1/3.02和JDK1.4開發, 您還需要Spring 1.2.5和Axis1.3提供的支持。源碼下載。
2.Web Service調用
Web Service是目前實現SOA應用的一項基本的,適用的技術,它為服務的訪問提供了一個被廣泛接受的開放標準。為了便于說明問題,我們將使用XMethods 網站(http://www.xmethods.net/)發布的貨幣兌換服務作為示例。并針對JAX-RPC 1.1,說明如何編寫Web Service 的調用代碼。
2.1 示例說明
http://xmethods.net 作為最早推出Web Service實際示例的網站,提供了很多優秀的Web Service 樣例。其中有一個匯率計算服務,可以返回兩個國家之間的貨幣兌換比例。獲取該服務的詳細信息,請參考該服務的服務描述文檔(獲取WSDL 文檔) 。在此就不具體解析該服務描述文檔了。讀者可以從WSDL2Java生成的接口中了解該服務的用法:
public interface CurrencyExchangePortType extends java.rmi.Remote { public float getRate(String country1, String country2) throws java.rmi.RemoteException; } |
2.2 客戶端調用方法
JAX-RPC作為Java平臺的RPC服務調用標準接口,為Web Service客戶端調用提供了3種方法,分別是DII,動態代理,和靜態Stub。 DII(Dynamic Invocation Interface)采用直接調用方式,可以在程序中設置諸多的調用屬性,使用較為靈活,但是調用過程卻相對繁瑣復雜,易造成代碼膨脹且可重用性低,每次調用不同的Web Service都要重復進行大量編碼。
JAX-RPC中動態代理(Dynamic Proxy)的方法實現對Web Service的動態調用,可以在運行時根據用戶定義的Client端接口創建適配對象。從而避免了直接操作底層的接口,減少了客戶端的冗余,屏蔽了調用相關的復雜性。
使用靜態Stub和Service Locator是目前最常用的調用方式。JAX-RPC使用靜態的Stub方式包裝對底層接口的調用,從而提供一種更為簡便的調用方式。使用該方式需要利用支持環境(比如Axis)所提供的工具根據WSDL預生成Web Service客戶端的實現代碼。因此如果服務的WSDL發生變化,就必須重新生成新的客戶端代碼并進行重新部署。
為了更詳細的了解靜態Stub的調用方式,您可以將示例代碼的WebServiceClient.jar導入到您現有Eclipse工作區之中。
客戶端生成代碼包括如下4個類:如圖 1 所示:
圖 1: 客戶端代碼類圖
在上圖中包括的幾個類中:
使用Stub調用Web Service的過程也非常簡單,讀者可以參考清單 1:
清單 1:Web Service 調用代碼示例
try { //創建ServiceLocator CurrencyExchangeServiceLocator locator = new CurrencyExchangeServiceLocator(); //設定端點地址 URL endPointAddress = new URL("http://services.xmethods.net:80/soap"); //創建Stub實例 CurrencyExchangePortType stub = locator.getCurrencyExchangePort(endPointAddress); //設定超時為120秒 ((CurrencyExchangeBindingStub)stub).setTimeout(120000); //調用Web Service計算人民幣與美元的匯率 float newPrice = stub.getRate("China", "USA") * 100; } catch (MalformedURLException mex) { //... } catch (ServiceException sex) { //... } catch (RemoteException rex) { //... } |
3.重構Web Service調用代碼
3.1 實例代碼中的"壞味道"
上面的基于Service Locator的Web Service訪問代碼雖然簡單但暴露出以下幾個問題:
1.訪問Web Service所需的配置代碼被嵌入應用邏輯之中
在Web Service調用中,我們需要設定一系列必要的參數。比如:服務端點地址、用戶名/密碼、超時設定等等。這些參數在開發和運行環境中都有可能發生變化。我們必須提供一種機制:在環境變化時,不必修改源代碼就可以改變Web Service的訪問配置。
2 客戶端代碼與Web Service訪問代碼綁定
在上面的代碼中,業務邏輯與Web Service的Stub創建和配置代碼綁定在一起。這也不是一種良好的編程方式??蛻舳舜a只應關心服務的接口,而不應關心服務的實現和訪問細節。比如,我們既可以通過Web Service的方式訪問遠程服務,也可以通過EJB的方式進行訪問。訪問方式對業務邏輯應該是透明的。
這種分離客戶端代碼與服務訪問代碼的方式也有利于測試。這樣在開發過程中,負責集成的程序員就可能在遠程服務還未完全實現的情況下,基于服務接口編寫集成代碼,并通過編寫POJO(Plain Old Java Object)構建偽服務實現來進行單元測試和模擬運行。這種開發方式對于保證分布式系統代碼質量具有重要意義。
因此,為了解決上面的問題我們需要:
1、將Web Service訪問的配置管理與代碼分離;
2、解除客戶端代碼與遠程服務之間的依賴關系;
3.2 利用IoC模式進行重構代碼
我們先介紹在Core J2EE Patterns一書中提到的一種業務層模式:Business Delegate。它所要解決的問題是屏蔽遠程服務訪問的復雜性。它的主要思想就是將Business Delegate作為遠程服務的客戶端抽象,隱藏服務訪問細節。Business Delegate還可以封裝并改變服務調用過程,比如將遠程服務調用拋出的異常(例如RemoteException)轉換為應用級別的異常類型。
其類圖如圖 2 所示:
圖 2:Business Delegate 模式的類圖圖解
Business Delegate模式實現很好地實現了客戶端與遠程訪問代碼的解耦,但它并不關注Delegate與遠程服務之間的解耦。為了更好解決Business Delegate和遠程服務之間的依賴關系,并更好地進行配置管理,我們可以用IoC模式來加以解決。
IoC(Inversion of Contro)l意為控制反轉,其背后的概念常被表述為"好萊塢法則":"Don't call me, I'll call you." IoC將一部分責任從應用代碼交給framework(或者控制器)來做。通過IoC可以實現接口和具體實現的高度分離,降低對象之間的耦合程度。Spring是一個非常流行的IoC容器,它通過配置文件來定義對象的生命周期和依賴關系,并提供了良好的配置管理能力。
現在我們來重構我們的Web Service應用程序,我們首先為Business Delegate定義一個接口類型,它提供了一個應用級組件接口,所有客戶端都應通過它來執行匯率計算,而不必關心實現細節,如清單 2 所示:
清單 2:接口定義的代碼示例
Public interface CurrencyExchangeManager { //貨幣兌換計算 //新價格 = 匯率 * 價格 public float calculate(String country1, String country2, float price) throws CurrencyExchangeException; } |
Business Delegate的實現非常簡單,主要工作是包裝匯率計算 Web Service的調用,如清單 3 所示。
清單 3:Business Delegate的代碼示例
public class CurrencyExchangeManagerImpl implements CurrencyExchangeManager { //服務實例 private CurrencyExchangePortType stub; //獲取服務實例 public CurrencyExchangePortType getStub() { return stub; } //設定服務實例 public void setStub(CurrencyExchangePortType stub) { this.stub = stub; } //實現貨幣兌換 public float calculate(String country1, String country2, float price) throws CurrencyExchangeException { try { //通過Stub調用WebService float rate = stub.getRate(country1, country2); return rate * price; } catch (RemoteException rex) { throw new CurrencyExchangeException( "Failed to get exchange rate!", rex); } } } |
共3頁: 1 [2] [3] 下一頁 |