標籤:

SOA實踐 -- 使用IoC和AOP重構SOA應用

SOA實踐 -- 使用IoC和AOP重構SOA應用

文檔選項

將此頁作為電子郵件發送

級別: 初級

立 易, IBM 中國軟體開發實驗室 SOA設計中心 高級軟體工程師勇 趙, IBM 中國軟體開發實驗室 SOA設計中心 高級軟體工程師

2006 年 4 月 20 日

在本文中,作者通過一個Web Service訪問的實例,具體描述了SOA應用中所遇到的一系列具體問題,並描述如何利用IoC和AOP等技術進行代碼重構,從而構建結構更加良好、靈活的SOA應用。

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: 客戶端代碼類圖

在上圖中包括的幾個類中:

CurrencyExchangePortType:服務端點介面,定義了Web Service的方法簽名。

CurrencyExchangeService:Service介面,定義了獲取服務端點介面的方法。

CurrencyExchangeServiceLocator:ServiceLocator類,實現了Service介面。

CurrencyExchangeBindingStub: Stub實現類,實現了服務端點介面,封裝了對Web Service訪問的底層邏輯。

使用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創建和配置代碼綁定在一起。這也不是一種良好的編程方式。客戶端代碼只應關心服務的介面,而不應關心服務的實現和訪問細節。比 如,我們既可以通過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); } }}

下面我們需要討論如何利用Spring的IoC機制,來創建和配置對象,並定義它們的依賴關係。

Spring利用類工廠來創建和配置對象。在Spring框架中,已經為基於JAX-RPC的Web Service調用提供了一個客戶端代理的類工廠實現:JaxRpcPortProxyFactoryBean。在配置文件bean.xml中,我們將使 用JaxRpcPortProxyFactoryBean來創建和配置Web Service的客戶端代理"CurrencyExchangeService",如清單 5 所示。我們還將定義一個名為"CurrencyExchangeManager"的CurrencyExchangeManagerImpl實例,並建立 它與CurrencyExchangeService之間的依賴關係。有關Spring 配置和JaxRpcPortProxyFactoryBean的使用細節請參見參考資料。

清單 5:bean.xml的配置文件

<?xml version="1.0" encoding="utf-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN""http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean id="CurrencyExchangeService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean"> <property name="serviceInterface"> <value>net.xmethods.www.sd.CurrencyExchangeService_wsdl. CurrencyExchangePortType</value> </property> <property name="wsdlDocumentUrl"> <value>http://www.xmethods.net/sd/2001/CurrencyExchangeService. wsdl</value> </property> <property name="namespaceUri"> <value>http://www.xmethods.net/sd/CurrencyExchangeService. wsdl</value> </property> <property name="serviceName"> <value>CurrencyExchangeService</value> </property> <property name="portName"> <value>CurrencyExchangePort</value> </property> <property name="endpointAddress"> <value>http://services.xmethods.net:80/soap</value> </property> </bean> <bean id="CurrencyExchangeManager" class="test.ws.CurrencyExchangeManagerImpl"> <property name="stub"> <ref bean="CurrencyExchangeService"/> </property> </bean></beans>

最後我們創建一個測試程序來驗證我們的代碼,如清單6 所示:

清單 6:測試代碼

public class Main { // For test only public static void main(String[] args) { // Spring Framework將根據配置文件創建並配置CurrencyExchangeManager實例 ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml"); // 獲取CurrencyExchangeManager實例 CurrencyExchangeManager manager = (CurrencyExchangeManager) ctx .getBean("CurrencyExchangeManager"); try { System.out.println(manager.calculate("China", "USA", 100)); System.out.println(manager.calculate("China", "Japan", 200)); System.out.println(manager.calculate("China", "USA", 200)); } catch (Exception ex) { ex.printStackTrace(); } }}

此時運行測試客戶端,等待片刻將會看見測試結果,如清單 7 所示:

清單 7:測試結果。

12.342853.2624.68

註:該結果會隨著匯率的變化而出現不同的值。

該程序的類圖和順序圖如圖3及圖4所示:

圖 3:示常式序的類圖

從上面的類圖我們可以看到,我們的測試程序(Main.java)通過Spring框架獲取了BusinessDelegate的實例。而且 Spring 框架還會根據配置中的依賴關係,在運行時將Web Service的客戶端代理" 注射"到CurrencyExchangeManagerImpl實例中,這就是依賴注入(Dependency Injection)。通過這種方式解決了應用邏輯和BusinessDelegate之間的依賴關係,以及BusinessDelegate的實現與遠程服務之間的依賴關係,如圖 4 所示。

圖 4: 示常式序的順序圖

Spring框架提供的ApplicationContext實現會根據配置文件中的描述信息來實現對象生命周期管理,配置管理以及依賴管理等功 能。這一切對於應用程序是透明的,應用程序代碼只依賴介面進行編程,而無需考慮其它複雜問題。無論是Web Service的配置發生變化,或是改用不同的服務實現時,都不會對客戶端應用代碼的產生影響。這很好地實現了業務邏輯與Web Service調用之間的解耦。

3.3 構建自己的 Web Service代理工廠

Spring所提供的JaxRpcPortProxyFactoryBean封裝了構造Web Service客戶端代理的細節,可以通過參數配置來創建Dynamic Proxy和DII類型的Web Service客戶端代理。(如果您希望深入了解其實現細節可以參考org.springframework.remoting.jaxrpc包下的源代 碼。)但由於JaxRpcPortProxyFactoryBean需要使用者對WSDL中Port,Service,名空間等概念有深入的了解;而且如 果Web Service使用了複雜數據類型,開發人員需要手工定義類型映射代碼。所以JaxRpcPortProxyFactoryBean並不適合Web Service的初學者來使用。

為了進一步簡化Web Service代理的創建,並幫助讀者更好地理解類工廠在Spring框架下的作用。我們提供了一個基於靜態Stub的Web Service客戶端代理工廠實現。其核心代碼非常簡單,就是通過ServiceLocator提供的方法來創建Web Service客戶端代理。

其主要代碼如清單8所示:

清單8:靜態代理工廠的代碼

public class WebServiceStubFactoryBean implements FactoryBean, InitializingBean { private Class serviceInterface; private Class serviceLocator; private Object stub; … public void afterPropertiesSet() throws Exception { //利用serviceLocator和服務介面創建Web Service客戶端代理 stub = ((javax.xml.rpc.Service) serviceLocator.newInstance()).getPort(serviceInterface); //為Stub設定endpointAddress,usernam, 超時等參數 preparePortStub((javax.xml.rpc.Stub) stub); } public Object getObject() { // 返回客戶端代理 return stub; } public Class getObjectType() { // 返回服務介面 return serviceInterface; } public boolean isSingleton() { return true; }}

我們需要修改配置文件bean.xml中有關Web Service代理創建的部分,讓新的Web Service 代理工廠發揮作用。如清單9所示:

清單9:修改後的bean.xml的配置文件

<bean id="CurrencyExchangeService" class="test.ws.WebServiceStubFactoryBean"> <property name="serviceInterface"> <value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.CurrencyExchangePortType</value> </property> <property name="serviceLocator"> <value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.CurrencyExchangeServiceLocator</value> </property> <property name="endpointAddress2"> <value>http://services.xmethods.net:80/soap</value> </property> <property name="timeout"> <value>120000</value> </property></bean>

得益於Spring框架,雖然我們已經替換了對象的類工廠,卻並不需要更改應用代碼。通過Spring框架的IoC機制,我們可以完全使用面向介面的編程方式,而將實現的創建、配置和依賴管理交由Spring在運行時完成。即使實現發生了變化,也不需要改變應用程序結構。

回頁首

4.新的思考

故事並沒有結束,在開發過程中,我們又遇到了一系列關於Web Service調用的問題。

4.1性能

系統性能是分散式應用中的一個重要問題。許多用戶都擔心由Web Service技術所引入的額外開銷是否會影響到產品的性能。隨著技術的不斷發展,Web Service引擎性能已經有了很大提高,一般來說使用Web Service的系統的性能可以滿足絕大部分應用的需求。但在特定情況下,如果系統性能無法滿足客戶需求,我們首先需要對系統性能進行科學地分析和測定才 能定位真正的性能瓶頸。這個問題在上文簡單的示例中並不難解決,只需要在Web Service調用前後加入日誌代碼記錄調用時間即可實現。但在實際系統中,比如一個產品目錄的Web Service可能提供數十種查詢方法,而程序中很多組件都會依賴於該服務提供的查詢功能。如果在系統中所有的地方加入性能測定代碼,這個工作就變得非常 繁瑣和困難。我們需要用一種更加優雅的解決方式,在增添新功能的同時並不影響系統代碼或結構。

4.2緩存

在項目實踐中,一個有效的改善Web Service系統性能的方法就是利用緩存來減少Web Service的重複調用。在具體實現中我們可以採用客戶端緩存和伺服器端緩存等不同方式,他們具有不同的特點和適用範圍。在本文例子中,我們希望實現客 戶端緩存來提高系統性能。但由於Web Service業務邏輯的差別,我們希望能夠為特定的Web Service提供特定的緩存策略,而且這些策略應該是能夠被靈活配置的,它們不應於應用程序的邏輯代碼耦合在一起。

4.3故障恢復:

對於Web Service應用,系統的可用性也是一個需要考慮的重要問題。在運行時由於網路運行環境的複雜性和不確定性,用戶希望能夠對Web Service訪問提供一定的故障恢復機制:比如重試或者訪問備份服務(當系統在調用Web Service失敗後,使用備份Web Service的服務地址來繼續訪問)。這些故障恢復策略應該是可配置的,對應用邏輯透明的。

回頁首

5.使用AOP解決SOA應用中的Crosscutting Concern

通過對上邊一系列問題的分析,讀者也許會發現這些問題並不是Web Service訪問的核心問題,但會影響系統中許多不同的組件。而且其中一些問題需要我們能夠靈活配置不同的實現策略,因此我們不應該將處理這些問題的代碼與應用代碼混合。

下面我們將利用AOP(Aspect-Oriented Programming)提供的方法來解決上述的問題。AOP是一種新興的方法學,它最基本的概念就是關注隔離(Separation of Concern)。AOP提供了一系列的技術使得我們能夠從代碼中分離那些影響到許多系統模塊的crosscutting concerns,並將他們模塊化為Aspects。AOP的主要目的仍然是解耦,在分離關注點後,才能將關注點的變更控制一定範圍內,增加程序的靈活 性,才能使得關注能夠根據需求和環境作出隨時調整。

我們將利用Spring所提供的AOP功能支持來解決以上問題。這裡我們只簡單地介紹涉及到的AOP基本概念以及實現,如果您希望更好地了解AOP的概念以及Spring AOP支持的細節請參見參考資料。

  • Joinpoint 是程序的運行點。在Spring AOP中,一個Joinpoint對應著一個方法調用。
  • Advice 定義了AOP框架在特定的Joinpoint的處理邏輯。Spring AOP框架通過interceptor方式實現了advice,並且提供了多種advice類型。其中最基本的"around advice"會在一個方法調用之前和之後被執行。
  • 下面我們將利用Spring提供的MethodInterceptor來為Web Service調用實現我們的定義的處理邏輯。

    5.1 PerformanceMonitorInterceptor

    性能測量是AOP最簡單的例子之一,我們可以直接利用Spring提供的實現在bean.xml中聲明我們的WebServicePerformanceMonitorInterceptor。

    5.2 CacheInterceptor

    為 了不引入緩存策略的複雜性,我們只提供了一個利用HashMap的簡單實現:它利用 Web Service的調用參數列表作為HashMap鍵值。在Web Service調用之前,首先檢查緩存中是否擁有與現在參數列表相同的項,如果有則返回緩存的結果,否則調用Web Service並將<參數列表,結果>記錄在HashMap中。在實際應用中,您應該根據具體情況來選擇、構造適合Web Service的業務特性的Cache實現,也可以採用成熟的Cache實現。

    在下面代碼實現中有一個生成Web Service調用主鍵的小技巧。因為Web Service引擎要求所有調用參數必須是可序列化的,所以我們可以利用Java提供的序列化功能來實現對象的克隆。如清單10所示:

    清單10:SimpleCacheInterceptor的代碼示例

    public class SimpleCacheInterceptor implements MethodInterceptor { private Map cache = new HashMap(); private Object cloneObject(Object obj) throws Exception { Object newObj = null; if (obj != null) { // 通過序列化/反序列化來克隆對象 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(obj); out.flush(); out.close(); ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream(bos.toByteArray())); newObj = in.readObject(); } return newObj; } //基於參數列表數組,生成用於HashMap的鍵值public Object generateKey(Object[] args) throws Exception { Object[] newArgs = (Object[]) cloneObject(args); List key = Arrays.asList(newArgs); return key; } //實現使用緩存技術的invoke方法 public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object result = null; Object data = null; Object key = null; try { key = generateKey(methodInvocation.getArguments()); data = cache.get(key); } catch (Exception ex) { logger.error("Failed to find from the cache", ex); } if (data == null) { //如果Cache中沒有緩存結果,調用服務執行生成用於HashMap的鍵值 result = methodInvocation.proceed(); try { data = cloneObject(result); cache.put(key, data); } catch (Exception ex) { logger.error("Failed to cache the result!", ex); } } else { result = data; } return result; }}

    5.3 FailoverInterceptor

    下面代碼提供了一個基於服務備份切換的故障恢復實現,在運行時,如果Interceptor檢測到服務調用由於網路故障拋出異常時,它將使用備份服務的端點地址並重新調用。如清單11所示:

    清單 11: SimpleFailoverInterceptor的代碼示例

    public class SimpleFailoverInterceptor implements MethodInterceptor { … … //實現支持端點運行時切換的invoke方法 public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object result = null; try { result = methodInvocation.proceed(); } catch (Throwable ex) { if (isNetworkFailure(ex)) { //切換服務端點地址 switchEndPointAddress((Stub) methodInvocation.getThis()); result = methodInvocation.proceed(); } else { throw ex; } } return result; }}

    為了支持備份服務切換的功能,我們在WebServicePortProxyFactoryBean中為填加了配置參數"endpointAddress2",它會在創建的Web Service客戶端代理對象中記錄備份URL。

    我們可以在CurrencyExchangeService加入下列參數來試驗SimpleFailoverInterceptor的功能。其中第 一個端點地址為一個錯誤的URL。在第一次調用服務時,SimpleFailoverInterceptor會偵測到網路故障的發生,並自動切換使用第二 個端點地址繼續訪問。如清單12所示:

    清單12:配置文件種增加的屬性

    <property name="endpointAddress"> <value>http://localhost/wrong_endpoint_address</value></property><property name="endpointAddress2"> <value>http://services.xmethods.net:80/soap</value></property>

    5.4配置文件和運行結果

    現在我們需要在Spring配置文件中,為所有interceptor添加定義,並描述如何為CurrencyExchangeService構建 AOP Proxy。需要指出的是,我們要在interceptorName列表中聲明interceptor鏈的調用順序,還要將原有 CurrencyExchangeManager引用的stub對象替換為新AOP Proxy。如清單13所示:

    清單13:修改後的配置文件片段

    <bean id="WebServicePerformanceMonitorInterceptor"class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"> <property name="prefix"> <value>Web Service </value> </property> <property name="suffix"> <value></value> </property></bean><bean id="CacheInterceptor" class="test.ws.SimpleCacheInterceptor"/><bean id="FailoverInterceptor" class="test.ws.SimpleFailoverInterceptor"/> <bean id="CurrencyExchangeProxy"class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>net.xmethods.www.sd.CurrencyExchangeService_wsdl. CurrencyExchangePortType</value> </property> <property name="target"> <ref local="CurrencyExchangeService"/> </property> <property name="interceptorNames"> <list> <value>WebServicePerformanceMonitorInterceptor</value> <value>CacheInterceptor</value> <value>FailoverInterceptor</value> </list> </property></bean> <bean id="CurrencyExchangeManager"class="test.ws.CurrencyExchangeManagerImpl"> <property name="stub"> <ref bean="CurrencyExchangeProxy"/> </property></bean>

    這裡我們通過為AOP 的ProxyFactoryBean為 Web Service Stub創建了一個AOP代理,並且建立了一個Interceptor鏈。這樣在調用Web Service時,Spring框架會依次調用Interceptor執行。實例執行的順序圖將如圖5所示:

    圖5系統運行順序圖

    5.5 Interceptor與JAX-RPC Handler的關係與區別

    SOAP Message Handler是JAX-RPC為用戶自定義Web Service處理過程提供的一種擴展機制。在處理Web Service請求/響應過程中,Web Service 引擎會根據部署描述中的定義,按照一定的次序調用Handler的處理代碼。用戶編寫的Handler實現可以截獲並修改Web Service消息和處理流程,從而實現對Web Service引擎處理行為的定製和增強。

    比如,我們可以實現一個伺服器端Handler,記錄Web Service在受到請求消息和發出響應消息之間的時間間隔來實現對伺服器端業務性能的測定。而且我們只需在部署描述中增加Handler聲明即可,無需修改任何伺服器端代碼。

    從此可以看出,JAX-RPC Handler與我們在上文中所提供的AOP Interceptor都可以幫助我們的SOA應用程序實現關注分離(Separate Concern)的目標,在不改變應用代碼的同時,增強或改變Web Service服務訪問的功能。雖然我們可以利用它們實現一些類似的功能,但它們具有著不同的特點和適用範圍。

    JAX-RPC Handler是Web Service引擎的擴展機制。如果我們需要實現對SOAP消息進行的修改和處理,加入自定義的SOAP Header或對消息內容進行加密,Handler是我們的最佳選擇。而AOP是針對對象級別的擴展機制,它更適合對應用層邏輯進行操作。

    比如,我們在上文展示的利用AOP實現的CacheInterceptor,它緩存的是Web Service調用參數和結果。而我們也可以通過JAX-RPC Handler實現一個面向SOAP消息的實現,它將緩存Web Service的請求消息和響應消息。這兩個實現相比,基於AOP的實現更加簡單、直觀、快速、對資源消耗也比較小。而面向SOAP消息的實現則更加靈 活,對於不採用RPC方式的Web Service訪問也能提供支持。

    所以在具體的實踐過程中,開發人員應該根據具體的需求選擇合適的技術,也可以將這兩種技術結合使用。

    回頁首

    6.總結

    "分而治之"的方法是人們解決複雜問題的一種常見做法。而IoC、AOP等技術都體現了這種思想。通過更好的切分程序邏輯,使得程序結構更加良好,更加富有彈性,易於變化。也使得開發人員可以更加專註於業務邏輯本身,而將一部分其他邏輯交給容器和框架進行處理。

    在本文中,我們通過一個Web Service訪問的實例,具體描述了SOA應用中所遇到的一系列具體問題,並描述如何利用IoC和AOP等技術進行代碼重構,構建更加結構良好、靈活的SOA應用。綜上所述,我們可以看到:

    1使用IoC框架來實現對象的生命周期管理、配置管理和依賴管理,可以解除業務邏輯對服務調用的依賴關係;

    2 使用AOP方法來解決Web Service調用中的crosscutting concerns,將為系統增加新的功能而不必更改應用程序。

    3通過IoC和AOP來屏蔽Web Service訪問的複雜性,使得開發人員可以更加專註於業務邏輯本身,也使得系統更加穩定和富有彈性。

    回頁首

    下載

    描述 名字 大小 下載方法
    code sample code.zip 27 KB HTTP

    關於下載方法的信息

    Get Adobe? Reader?

    參考資料

  • JAX-RPC: http://java.sun.com/xml/jaxrpc/index.html
  • Spring 參考文檔 : http://www.springframework.org/docs/reference/remoting.html
  • 參考文章: SOA adventures, Part 1: Ease Web services invocation with dynamic decoupling
  • 關於AOP: 請參考IBM developerWorks上的更多資源。
  • J2EE核心模式: Core J2EE? Patterns: Best Practices and Design Strategies, Second Edition By Deepak Alur, John Crupi, Dan Malks
  • 作者簡介

    易立 IBM 中國軟體開發實驗室 SOA設計中心 高級軟體工程師。

    趙勇 IBM 中國軟體開發實驗室 SOA設計中心 軟體工程師。

    推薦閱讀:

    APP解構重構:勿忘初心
    潘知常:美學的重構:以超越維度與終極關懷為視域
    禮與法:二程重構政治秩序的雙重路徑
    汪舒明:「基地」組織的復興和重構

    TAG:重構 | 實踐 |