在本文中,作者通過一個(gè)Web Service訪問的實(shí)例,具體描述了SOA應(yīng)用中所遇到的一系列具體問題,并描述如何利用IoC和AOP等技術(shù)進(jìn)行代碼重構(gòu),從而構(gòu)建結(jié)構(gòu)更加良好、靈活的SOA應(yīng)用。
1.引言
SOA是一種構(gòu)造分布式系統(tǒng)的方法,它將業(yè)務(wù)應(yīng)用功能以服務(wù)的形式提供出來,以便更好的復(fù)用、組裝和與外部系統(tǒng)集成,從而降低開發(fā)成本,提高開發(fā)效率。SOA的目標(biāo)是為企業(yè)構(gòu)建一個(gè)靈活,可擴(kuò)展的IT基礎(chǔ)架構(gòu)來更好地支持隨需應(yīng)變的商務(wù)應(yīng)用。
隨著SOA技術(shù)和產(chǎn)品的不斷成熟,現(xiàn)在越來越多的用戶開始了解并認(rèn)同SOA的理念,但對(duì)SOA項(xiàng)目的實(shí)施還缺乏信心。其主要原因是:SOA應(yīng)用開發(fā)還相對(duì)比較復(fù)雜。
一年多來,本文作者所在的部門已經(jīng)從事了許多國(guó)內(nèi)外的SOA項(xiàng)目的實(shí)施和支持工作,積累了許多SOA應(yīng)用開發(fā)經(jīng)驗(yàn)。我們希望能夠通過一系列的文章與讀者分享這些想法,幫助您更好地構(gòu)建SOA應(yīng)用。
本文將從Web Service調(diào)用入手,在解決一系列具體問題的過程中,使用IoC (Inversion of Control) 和AOP
(Aspect- Oriented Programming) 等方法重構(gòu)Web Service的訪問代碼,使得業(yè)務(wù)邏輯與Web
Service訪問解耦,為您提供一個(gè)更加靈活和易于擴(kuò)展的訪問模式。
Spring是一個(gè)流行的輕量級(jí)容器,對(duì)IoC和AOP提供了良好的支持。本文為您提供了一個(gè)基于Spring的實(shí)現(xiàn)供您下載學(xué)習(xí)。示例代碼工程使
用Eclipse3.1/3.02和JDK1.4開發(fā), 您還需要Spring
1.2.5和Axis1.3提供的支持。詳細(xì)的下載信息請(qǐng)參見參考資源部分。
2.Web Service調(diào)用
Web Service是目前實(shí)現(xiàn)SOA應(yīng)用的一項(xiàng)基本的,適用的技術(shù),它為服務(wù)的訪問提供了一個(gè)被廣泛接受的開放標(biāo)準(zhǔn)。為了便于說明問題,我們將使用XMethods 網(wǎng)站(http://www./)發(fā)布的貨幣兌換服務(wù)作為示例。并針對(duì)JAX-RPC 1.1,說明如何編寫Web Service 的調(diào)用代碼。
2.1 示例說明
http:// 作為最早推出Web Service實(shí)際示例的網(wǎng)站,提供了很多優(yōu)秀的Web Service 樣例。其中有一個(gè)匯率計(jì)算服務(wù),可以返回兩個(gè)國(guó)家之間的貨幣兌換比例。獲取該服務(wù)的詳細(xì)信息,請(qǐng)參考該服務(wù)的服務(wù)描述文檔(獲取WSDL 文檔) 。在此就不具體解析該服務(wù)描述文檔了。讀者可以從WSDL2Java生成的接口中了解該服務(wù)的用法:
public interface CurrencyExchangePortType extends java.rmi.Remote { public float getRate(String country1, String country2) throws java.rmi.RemoteException; }
|
2.2 客戶端調(diào)用方法
JAX-RPC作為Java平臺(tái)的RPC服務(wù)調(diào)用標(biāo)準(zhǔn)接口,為Web Service客戶端調(diào)用提供了3種方法,分別是DII,動(dòng)態(tài)代理,和靜態(tài)Stub。
DII(Dynamic Invocation Interface)采用直接調(diào)用方式,可以在程序中設(shè)置諸多的調(diào)用屬性,使用較為靈活,但是調(diào)用過程卻相對(duì)繁瑣復(fù)雜,易造成代碼膨脹且可重用性低,每次調(diào)用不同的Web Service都要重復(fù)進(jìn)行大量編碼。
JAX-RPC中動(dòng)態(tài)代理(Dynamic Proxy)的方法實(shí)現(xiàn)對(duì)Web Service的動(dòng)態(tài)調(diào)用,可以在運(yùn)行時(shí)根據(jù)用戶定義的Client端接口創(chuàng)建適配對(duì)象。從而避免了直接操作底層的接口,減少了客戶端的冗余,屏蔽了調(diào)用相關(guān)的復(fù)雜性。
使用靜態(tài)Stub和Service
Locator是目前最常用的調(diào)用方式。JAX-RPC使用靜態(tài)的Stub方式包裝對(duì)底層接口的調(diào)用,從而提供一種更為簡(jiǎn)便的調(diào)用方式。使用該方式需要利
用支持環(huán)境(比如Axis)所提供的工具根據(jù)WSDL預(yù)生成Web
Service客戶端的實(shí)現(xiàn)代碼。因此如果服務(wù)的WSDL發(fā)生變化,就必須重新生成新的客戶端代碼并進(jìn)行重新部署。
為了更詳細(xì)的了解靜態(tài)Stub的調(diào)用方式,您可以將示例代碼的WebServiceClient.jar導(dǎo)入到您現(xiàn)有Eclipse工作區(qū)之中。
客戶端生成代碼包括如下4個(gè)類:如圖 1 所示:
圖 1: 客戶端代碼類圖
在上圖中包括的幾個(gè)類中:
CurrencyExchangePortType:服務(wù)端點(diǎn)接口,定義了Web Service的方法簽名。
CurrencyExchangeService:Service接口,定義了獲取服務(wù)端點(diǎn)接口的方法。
CurrencyExchangeServiceLocator:ServiceLocator類,實(shí)現(xiàn)了Service接口。
CurrencyExchangeBindingStub: Stub實(shí)現(xiàn)類,實(shí)現(xiàn)了服務(wù)端點(diǎn)接口,封裝了對(duì)Web Service訪問的底層邏輯。
使用Stub調(diào)用Web Service的過程也非常簡(jiǎn)單,讀者可以參考清單 1:
清單 1:Web Service 調(diào)用代碼示例
try { //創(chuàng)建ServiceLocator CurrencyExchangeServiceLocator locator = new CurrencyExchangeServiceLocator(); //設(shè)定端點(diǎn)地址 URL endPointAddress = new URL("http://services.:80/soap"); //創(chuàng)建Stub實(shí)例 CurrencyExchangePortType stub = locator.getCurrencyExchangePort(endPointAddress); //設(shè)定超時(shí)為120秒 ((CurrencyExchangeBindingStub)stub).setTimeout(120000); //調(diào)用Web Service計(jì)算人民幣與美元的匯率 float newPrice = stub.getRate("China", "USA") * 100; } catch (MalformedURLException mex) { //... } catch (ServiceException sex) { //... } catch (RemoteException rex) { //... }
|
3.重構(gòu)Web Service調(diào)用代碼
3.1 實(shí)例代碼中的"壞味道"
上面的基于Service Locator的Web Service訪問代碼雖然簡(jiǎn)單但暴露出以下幾個(gè)問題:
1.訪問Web Service所需的配置代碼被嵌入應(yīng)用邏輯之中
在Web Service調(diào)用中,我們需要設(shè)定一系列必要的參數(shù)。比如:服務(wù)端點(diǎn)地址、用戶名/密碼、超時(shí)設(shè)定等等。這些參數(shù)在開發(fā)和運(yùn)行環(huán)境中都有可能發(fā)生變化。我們必須提供一種機(jī)制:在環(huán)境變化時(shí),不必修改源代碼就可以改變Web Service的訪問配置。
2 客戶端代碼與Web Service訪問代碼綁定
在上面的代碼中,業(yè)務(wù)邏輯與Web
Service的Stub創(chuàng)建和配置代碼綁定在一起。這也不是一種良好的編程方式。客戶端代碼只應(yīng)關(guān)心服務(wù)的接口,而不應(yīng)關(guān)心服務(wù)的實(shí)現(xiàn)和訪問細(xì)節(jié)。比
如,我們既可以通過Web Service的方式訪問遠(yuǎn)程服務(wù),也可以通過EJB的方式進(jìn)行訪問。訪問方式對(duì)業(yè)務(wù)邏輯應(yīng)該是透明的。
這種分離客戶端代碼與服務(wù)訪問代碼的方式也有利于測(cè)試。這樣在開發(fā)過程中,負(fù)責(zé)集成的程序員就可能在遠(yuǎn)程服務(wù)還未完全實(shí)現(xiàn)的情況下,基于服務(wù)接口編
寫集成代碼,并通過編寫POJO(Plain Old Java
Object)構(gòu)建偽服務(wù)實(shí)現(xiàn)來進(jìn)行單元測(cè)試和模擬運(yùn)行。這種開發(fā)方式對(duì)于保證分布式系統(tǒng)代碼質(zhì)量具有重要意義。
因此,為了解決上面的問題我們需要:
1、將Web Service訪問的配置管理與代碼分離;
2、解除客戶端代碼與遠(yuǎn)程服務(wù)之間的依賴關(guān)系;
3.2 利用IoC模式進(jìn)行重構(gòu)代碼
我們先介紹在Core J2EE Patterns一書中提到的一種業(yè)務(wù)層模式:Business
Delegate。它所要解決的問題是屏蔽遠(yuǎn)程服務(wù)訪問的復(fù)雜性。它的主要思想就是將Business
Delegate作為遠(yuǎn)程服務(wù)的客戶端抽象,隱藏服務(wù)訪問細(xì)節(jié)。Business
Delegate還可以封裝并改變服務(wù)調(diào)用過程,比如將遠(yuǎn)程服務(wù)調(diào)用拋出的異常(例如RemoteException)轉(zhuǎn)換為應(yīng)用級(jí)別的異常類型。
其類圖如圖 2 所示:
圖 2:Business Delegate 模式的類圖圖解
Business
Delegate模式實(shí)現(xiàn)很好地實(shí)現(xiàn)了客戶端與遠(yuǎn)程訪問代碼的解耦,但它并不關(guān)注Delegate與遠(yuǎn)程服務(wù)之間的解耦。為了更好解決Business
Delegate和遠(yuǎn)程服務(wù)之間的依賴關(guān)系,并更好地進(jìn)行配置管理,我們可以用IoC模式來加以解決。
IoC(Inversion of Contro)l意為控制反轉(zhuǎn),其背后的概念常被表述為"好萊塢法則":"Don‘t call me,
I‘ll call you."
IoC將一部分責(zé)任從應(yīng)用代碼交給framework(或者控制器)來做。通過IoC可以實(shí)現(xiàn)接口和具體實(shí)現(xiàn)的高度分離,降低對(duì)象之間的耦合程度。
Spring是一個(gè)非常流行的IoC容器,它通過配置文件來定義對(duì)象的生命周期和依賴關(guān)系,并提供了良好的配置管理能力。
現(xiàn)在我們來重構(gòu)我們的Web Service應(yīng)用程序,我們首先為Business Delegate定義一個(gè)接口類型,它提供了一個(gè)應(yīng)用級(jí)組件接口,所有客戶端都應(yīng)通過它來執(zhí)行匯率計(jì)算,而不必關(guān)心實(shí)現(xiàn)細(xì)節(jié),如清單 2 所示:
清單 2:接口定義的代碼示例
Public interface CurrencyExchangeManager { //貨幣兌換計(jì)算 //新價(jià)格 = 匯率 * 價(jià)格 public float calculate(String country1, String country2, float price) throws CurrencyExchangeException; }
|
Business Delegate的實(shí)現(xiàn)非常簡(jiǎn)單,主要工作是包裝匯率計(jì)算 Web Service的調(diào)用,如清單 3 所示。
清單 3:Business Delegate的代碼示例
public class CurrencyExchangeManagerImpl implements CurrencyExchangeManager { //服務(wù)實(shí)例 private CurrencyExchangePortType stub; //獲取服務(wù)實(shí)例 public CurrencyExchangePortType getStub() { return stub; } //設(shè)定服務(wù)實(shí)例 public void setStub(CurrencyExchangePortType stub) { this.stub = stub; } //實(shí)現(xiàn)貨幣兌換 public float calculate(String country1, String country2, float price) throws CurrencyExchangeException { try { //通過Stub調(diào)用WebService float rate = stub.getRate(country1, country2); return rate * price; } catch (RemoteException rex) { throw new CurrencyExchangeException( "Failed to get exchange rate!", rex); } } }
|
下面我們需要討論如何利用Spring的IoC機(jī)制,來創(chuàng)建和配置對(duì)象,并定義它們的依賴關(guān)系。
Spring利用類工廠來創(chuàng)建和配置對(duì)象。在Spring框架中,已經(jīng)為基于JAX-RPC的Web
Service調(diào)用提供了一個(gè)客戶端代理的類工廠實(shí)現(xiàn):JaxRpcPortProxyFactoryBean。在配置文件bean.xml中,我們將使
用JaxRpcPortProxyFactoryBean來創(chuàng)建和配置Web
Service的客戶端代理"CurrencyExchangeService",如清單 5
所示。我們還將定義一個(gè)名為"CurrencyExchangeManager"的CurrencyExchangeManagerImpl實(shí)例,并建立
它與CurrencyExchangeService之間的依賴關(guān)系。有關(guān)Spring
配置和JaxRpcPortProxyFactoryBean的使用細(xì)節(jié)請(qǐng)參見參考資料。
清單 5:bean.xml的配置文件
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www./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./sd/2001/CurrencyExchangeService. wsdl</value> </property> <property name="namespaceUri"> <value>http://www./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.:80/soap</value> </property> </bean> <bean id="CurrencyExchangeManager" class="test.ws.CurrencyExchangeManagerImpl"> <property name="stub"> <ref bean="CurrencyExchangeService"/> </property> </bean> </beans>
|
最后我們創(chuàng)建一個(gè)測(cè)試程序來驗(yàn)證我們的代碼,如清單6 所示:
清單 6:測(cè)試代碼
public class Main { // For test only public static void main(String[] args) { // Spring Framework將根據(jù)配置文件創(chuàng)建并配置CurrencyExchangeManager實(shí)例 ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml"); // 獲取CurrencyExchangeManager實(shí)例 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(); } } }
|
此時(shí)運(yùn)行測(cè)試客戶端,等待片刻將會(huì)看見測(cè)試結(jié)果,如清單 7 所示:
清單 7:測(cè)試結(jié)果。
注:該結(jié)果會(huì)隨著匯率的變化而出現(xiàn)不同的值。
該程序的類圖和順序圖如圖3及圖4所示:
圖 3:示例程序的類圖
從上面的類圖我們可以看到,我們的測(cè)試程序(Main.java)通過Spring框架獲取了BusinessDelegate的實(shí)例。而且
Spring 框架還會(huì)根據(jù)配置中的依賴關(guān)系,在運(yùn)行時(shí)將Web Service的客戶端代理"
注射"到CurrencyExchangeManagerImpl實(shí)例中,這就是依賴注入(Dependency Injection)。通過這種方式解決了應(yīng)用邏輯和BusinessDelegate之間的依賴關(guān)系,以及BusinessDelegate的實(shí)現(xiàn)與遠(yuǎn)程服務(wù)之間的依賴關(guān)系,如圖 4 所示。
圖 4: 示例程序的順序圖
Spring框架提供的ApplicationContext實(shí)現(xiàn)會(huì)根據(jù)配置文件中的描述信息來實(shí)現(xiàn)對(duì)象生命周期管理,配置管理以及依賴管理等功
能。這一切對(duì)于應(yīng)用程序是透明的,應(yīng)用程序代碼只依賴接口進(jìn)行編程,而無需考慮其它復(fù)雜問題。無論是Web
Service的配置發(fā)生變化,或是改用不同的服務(wù)實(shí)現(xiàn)時(shí),都不會(huì)對(duì)客戶端應(yīng)用代碼的產(chǎn)生影響。這很好地實(shí)現(xiàn)了業(yè)務(wù)邏輯與Web
Service調(diào)用之間的解耦。
3.3 構(gòu)建自己的 Web Service代理工廠
Spring所提供的JaxRpcPortProxyFactoryBean封裝了構(gòu)造Web
Service客戶端代理的細(xì)節(jié),可以通過參數(shù)配置來創(chuàng)建Dynamic Proxy和DII類型的Web
Service客戶端代理。(如果您希望深入了解其實(shí)現(xiàn)細(xì)節(jié)可以參考o(jì)rg.springframework.remoting.jaxrpc包下的源代
碼。)但由于JaxRpcPortProxyFactoryBean需要使用者對(duì)WSDL中Port,Service,名空間等概念有深入的了解;而且如
果Web
Service使用了復(fù)雜數(shù)據(jù)類型,開發(fā)人員需要手工定義類型映射代碼。所以JaxRpcPortProxyFactoryBean并不適合Web
Service的初學(xué)者來使用。
為了進(jìn)一步簡(jiǎn)化Web
Service代理的創(chuàng)建,并幫助讀者更好地理解類工廠在Spring框架下的作用。我們提供了一個(gè)基于靜態(tài)Stub的Web
Service客戶端代理工廠實(shí)現(xiàn)。其核心代碼非常簡(jiǎn)單,就是通過ServiceLocator提供的方法來創(chuàng)建Web Service客戶端代理。
其主要代碼如清單8所示:
清單8:靜態(tài)代理工廠的代碼
public class WebServiceStubFactoryBean implements FactoryBean, InitializingBean { private Class serviceInterface; private Class serviceLocator; private Object stub; … public void afterPropertiesSet() throws Exception { //利用serviceLocator和服務(wù)接口創(chuàng)建Web Service客戶端代理 stub = ((javax.xml.rpc.Service) serviceLocator.newInstance()).getPort(serviceInterface); //為Stub設(shè)定endpointAddress,usernam, 超時(shí)等參數(shù) preparePortStub((javax.xml.rpc.Stub) stub); } public Object getObject() { // 返回客戶端代理 return stub; } public Class getObjectType() { // 返回服務(wù)接口 return serviceInterface; } public boolean isSingleton() { return true; } }
|
我們需要修改配置文件bean.xml中有關(guān)Web Service代理創(chuàng)建的部分,讓新的Web Service 代理工廠發(fā)揮作用。如清單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.:80/soap</value> </property> <property name="timeout"> <value>120000</value> </property> </bean>
|
得益于Spring框架,雖然我們已經(jīng)替換了對(duì)象的類工廠,卻并不需要更改應(yīng)用代碼。通過Spring框架的IoC機(jī)制,我們可以完全使用面向接口的編程方式,而將實(shí)現(xiàn)的創(chuàng)建、配置和依賴管理交由Spring在運(yùn)行時(shí)完成。即使實(shí)現(xiàn)發(fā)生了變化,也不需要改變應(yīng)用程序結(jié)構(gòu)。
4.新的思考
故事并沒有結(jié)束,在開發(fā)過程中,我們又遇到了一系列關(guān)于Web Service調(diào)用的問題。
4.1性能
系統(tǒng)性能是分布式應(yīng)用中的一個(gè)重要問題。許多用戶都擔(dān)心由Web
Service技術(shù)所引入的額外開銷是否會(huì)影響到產(chǎn)品的性能。隨著技術(shù)的不斷發(fā)展,Web
Service引擎性能已經(jīng)有了很大提高,一般來說使用Web
Service的系統(tǒng)的性能可以滿足絕大部分應(yīng)用的需求。但在特定情況下,如果系統(tǒng)性能無法滿足客戶需求,我們首先需要對(duì)系統(tǒng)性能進(jìn)行科學(xué)地分析和測(cè)定才
能定位真正的性能瓶頸。這個(gè)問題在上文簡(jiǎn)單的示例中并不難解決,只需要在Web
Service調(diào)用前后加入日志代碼記錄調(diào)用時(shí)間即可實(shí)現(xiàn)。但在實(shí)際系統(tǒng)中,比如一個(gè)產(chǎn)品目錄的Web
Service可能提供數(shù)十種查詢方法,而程序中很多組件都會(huì)依賴于該服務(wù)提供的查詢功能。如果在系統(tǒng)中所有的地方加入性能測(cè)定代碼,這個(gè)工作就變得非常
繁瑣和困難。我們需要用一種更加優(yōu)雅的解決方式,在增添新功能的同時(shí)并不影響系統(tǒng)代碼或結(jié)構(gòu)。
4.2緩存
在項(xiàng)目實(shí)踐中,一個(gè)有效的改善Web Service系統(tǒng)性能的方法就是利用緩存來減少Web
Service的重復(fù)調(diào)用。在具體實(shí)現(xiàn)中我們可以采用客戶端緩存和服務(wù)器端緩存等不同方式,他們具有不同的特點(diǎn)和適用范圍。在本文例子中,我們希望實(shí)現(xiàn)客
戶端緩存來提高系統(tǒng)性能。但由于Web Service業(yè)務(wù)邏輯的差別,我們希望能夠?yàn)樘囟ǖ腤eb
Service提供特定的緩存策略,而且這些策略應(yīng)該是能夠被靈活配置的,它們不應(yīng)于應(yīng)用程序的邏輯代碼耦合在一起。
4.3故障恢復(fù):
對(duì)于Web Service應(yīng)用,系統(tǒng)的可用性也是一個(gè)需要考慮的重要問題。在運(yùn)行時(shí)由于網(wǎng)絡(luò)運(yùn)行環(huán)境的復(fù)雜性和不確定性,用戶希望能夠?qū)eb
Service訪問提供一定的故障恢復(fù)機(jī)制:比如重試或者訪問備份服務(wù)(當(dāng)系統(tǒng)在調(diào)用Web Service失敗后,使用備份Web
Service的服務(wù)地址來繼續(xù)訪問)。這些故障恢復(fù)策略應(yīng)該是可配置的,對(duì)應(yīng)用邏輯透明的。
5.使用AOP解決SOA應(yīng)用中的Crosscutting Concern
通過對(duì)上邊一系列問題的分析,讀者也許會(huì)發(fā)現(xiàn)這些問題并不是Web Service訪問的核心問題,但會(huì)影響系統(tǒng)中許多不同的組件。而且其中一些問題需要我們能夠靈活配置不同的實(shí)現(xiàn)策略,因此我們不應(yīng)該將處理這些問題的代碼與應(yīng)用代碼混合。
下面我們將利用AOP(Aspect-Oriented
Programming)提供的方法來解決上述的問題。AOP是一種新興的方法學(xué),它最基本的概念就是關(guān)注隔離(Separation of
Concern)。AOP提供了一系列的技術(shù)使得我們能夠從代碼中分離那些影響到許多系統(tǒng)模塊的crosscutting
concerns,并將他們模塊化為Aspects。AOP的主要目的仍然是解耦,在分離關(guān)注點(diǎn)后,才能將關(guān)注點(diǎn)的變更控制一定范圍內(nèi),增加程序的靈活
性,才能使得關(guān)注能夠根據(jù)需求和環(huán)境作出隨時(shí)調(diào)整。
我們將利用Spring所提供的AOP功能支持來解決以上問題。這里我們只簡(jiǎn)單地介紹涉及到的AOP基本概念以及實(shí)現(xiàn),如果您希望更好地了解AOP的概念以及Spring AOP支持的細(xì)節(jié)請(qǐng)參見參考資料。
- Joinpoint 是程序的運(yùn)行點(diǎn)。在Spring AOP中,一個(gè)Joinpoint對(duì)應(yīng)著一個(gè)方法調(diào)用。
- Advice 定義了AOP框架在特定的Joinpoint的處理邏輯。Spring AOP框架通過interceptor方式實(shí)現(xiàn)了advice,并且提供了多種advice類型。其中最基本的"around advice"會(huì)在一個(gè)方法調(diào)用之前和之后被執(zhí)行。
下面我們將利用Spring提供的MethodInterceptor來為Web Service調(diào)用實(shí)現(xiàn)我們的定義的處理邏輯。
5.1 PerformanceMonitorInterceptor
性能測(cè)量是AOP最簡(jiǎn)單的例子之一,我們可以直接利用Spring提供的實(shí)現(xiàn)在bean.xml中聲明我們的WebServicePerformanceMonitorInterceptor。
5.2 CacheInterceptor
為
了不引入緩存策略的復(fù)雜性,我們只提供了一個(gè)利用HashMap的簡(jiǎn)單實(shí)現(xiàn):它利用 Web
Service的調(diào)用參數(shù)列表作為HashMap鍵值。在Web
Service調(diào)用之前,首先檢查緩存中是否擁有與現(xiàn)在參數(shù)列表相同的項(xiàng),如果有則返回緩存的結(jié)果,否則調(diào)用Web
Service并將<參數(shù)列表,結(jié)果>記錄在HashMap中。在實(shí)際應(yīng)用中,您應(yīng)該根據(jù)具體情況來選擇、構(gòu)造適合Web
Service的業(yè)務(wù)特性的Cache實(shí)現(xiàn),也可以采用成熟的Cache實(shí)現(xiàn)。
在下面代碼實(shí)現(xiàn)中有一個(gè)生成Web Service調(diào)用主鍵的小技巧。因?yàn)閃eb Service引擎要求所有調(diào)用參數(shù)必須是可序列化的,所以我們可以利用Java提供的序列化功能來實(shí)現(xiàn)對(duì)象的克隆。如清單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) { // 通過序列化/反序列化來克隆對(duì)象 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; } //基于參數(shù)列表數(shù)組,生成用于HashMap的鍵值 public Object generateKey(Object[] args) throws Exception { Object[] newArgs = (Object[]) cloneObject(args); List key = Arrays.asList(newArgs); return key; } //實(shí)現(xiàn)使用緩存技術(shù)的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中沒有緩存結(jié)果,調(diào)用服務(wù)執(zhí)行生成用于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
下面代碼提供了一個(gè)基于服務(wù)備份切換的故障恢復(fù)實(shí)現(xiàn),在運(yùn)行時(shí),如果Interceptor檢測(cè)到服務(wù)調(diào)用由于網(wǎng)絡(luò)故障拋出異常時(shí),它將使用備份服務(wù)的端點(diǎn)地址并重新調(diào)用。如清單11所示:
清單 11: SimpleFailoverInterceptor的代碼示例
public class SimpleFailoverInterceptor implements MethodInterceptor { … … //實(shí)現(xiàn)支持端點(diǎn)運(yùn)行時(shí)切換的invoke方法 public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object result = null; try { result = methodInvocation.proceed(); } catch (Throwable ex) { if (isNetworkFailure(ex)) { //切換服務(wù)端點(diǎn)地址 switchEndPointAddress((Stub) methodInvocation.getThis()); result = methodInvocation.proceed(); } else { throw ex; } } return result; } }
|
為了支持備份服務(wù)切換的功能,我們?cè)赪ebServicePortProxyFactoryBean中為填加了配置參數(shù)"endpointAddress2",它會(huì)在創(chuàng)建的Web Service客戶端代理對(duì)象中記錄備份URL。
我們可以在CurrencyExchangeService加入下列參數(shù)來試驗(yàn)SimpleFailoverInterceptor的功能。其中第
一個(gè)端點(diǎn)地址為一個(gè)錯(cuò)誤的URL。在第一次調(diào)用服務(wù)時(shí),SimpleFailoverInterceptor會(huì)偵測(cè)到網(wǎng)絡(luò)故障的發(fā)生,并自動(dòng)切換使用第二
個(gè)端點(diǎn)地址繼續(xù)訪問。如清單12所示:
清單12:配置文件種增加的屬性
<property name="endpointAddress"> <value>http://localhost/wrong_endpoint_address</value> </property> <property name="endpointAddress2"> <value>http://services.:80/soap</value> </property>
|
5.4配置文件和運(yùn)行結(jié)果
現(xiàn)在我們需要在Spring配置文件中,為所有interceptor添加定義,并描述如何為CurrencyExchangeService構(gòu)建
AOP
Proxy。需要指出的是,我們要在interceptorName列表中聲明interceptor鏈的調(diào)用順序,還要將原有
CurrencyExchangeManager引用的stub對(duì)象替換為新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創(chuàng)建了一個(gè)AOP代理,并且建立了一個(gè)Interceptor鏈。這樣在調(diào)用Web
Service時(shí),Spring框架會(huì)依次調(diào)用Interceptor執(zhí)行。實(shí)例執(zhí)行的順序圖將如圖5所示:
圖5系統(tǒng)運(yùn)行順序圖
5.5 Interceptor與JAX-RPC Handler的關(guān)系與區(qū)別
SOAP Message Handler是JAX-RPC為用戶自定義Web Service處理過程提供的一種擴(kuò)展機(jī)制。在處理Web
Service請(qǐng)求/響應(yīng)過程中,Web Service
引擎會(huì)根據(jù)部署描述中的定義,按照一定的次序調(diào)用Handler的處理代碼。用戶編寫的Handler實(shí)現(xiàn)可以截獲并修改Web
Service消息和處理流程,從而實(shí)現(xiàn)對(duì)Web Service引擎處理行為的定制和增強(qiáng)。
比如,我們可以實(shí)現(xiàn)一個(gè)服務(wù)器端Handler,記錄Web Service在受到請(qǐng)求消息和發(fā)出響應(yīng)消息之間的時(shí)間間隔來實(shí)現(xiàn)對(duì)服務(wù)器端業(yè)務(wù)性能的測(cè)定。而且我們只需在部署描述中增加Handler聲明即可,無需修改任何服務(wù)器端代碼。
從此可以看出,JAX-RPC Handler與我們?cè)谏衔闹兴峁┑腁OP
Interceptor都可以幫助我們的SOA應(yīng)用程序?qū)崿F(xiàn)關(guān)注分離(Separate
Concern)的目標(biāo),在不改變應(yīng)用代碼的同時(shí),增強(qiáng)或改變Web
Service服務(wù)訪問的功能。雖然我們可以利用它們實(shí)現(xiàn)一些類似的功能,但它們具有著不同的特點(diǎn)和適用范圍。
JAX-RPC Handler是Web
Service引擎的擴(kuò)展機(jī)制。如果我們需要實(shí)現(xiàn)對(duì)SOAP消息進(jìn)行的修改和處理,加入自定義的SOAP
Header或?qū)ο?nèi)容進(jìn)行加密,Handler是我們的最佳選擇。而AOP是針對(duì)對(duì)象級(jí)別的擴(kuò)展機(jī)制,它更適合對(duì)應(yīng)用層邏輯進(jìn)行操作。
比如,我們?cè)谏衔恼故镜睦肁OP實(shí)現(xiàn)的CacheInterceptor,它緩存的是Web
Service調(diào)用參數(shù)和結(jié)果。而我們也可以通過JAX-RPC Handler實(shí)現(xiàn)一個(gè)面向SOAP消息的實(shí)現(xiàn),它將緩存Web
Service的請(qǐng)求消息和響應(yīng)消息。這兩個(gè)實(shí)現(xiàn)相比,基于AOP的實(shí)現(xiàn)更加簡(jiǎn)單、直觀、快速、對(duì)資源消耗也比較小。而面向SOAP消息的實(shí)現(xiàn)則更加靈
活,對(duì)于不采用RPC方式的Web Service訪問也能提供支持。
所以在具體的實(shí)踐過程中,開發(fā)人員應(yīng)該根據(jù)具體的需求選擇合適的技術(shù),也可以將這兩種技術(shù)結(jié)合使用。
6.總結(jié)
"分而治之"的方法是人們解決復(fù)雜問題的一種常見做法。而IoC、AOP等技術(shù)都體現(xiàn)了這種思想。通過更好的切分程序邏輯,使得程序結(jié)構(gòu)更加良好,更加富有彈性,易于變化。也使得開發(fā)人員可以更加專注于業(yè)務(wù)邏輯本身,而將一部分其他邏輯交給容器和框架進(jìn)行處理。
在本文中,我們通過一個(gè)Web Service訪問的實(shí)例,具體描述了SOA應(yīng)用中所遇到的一系列具體問題,并描述如何利用IoC和AOP等技術(shù)進(jìn)行代碼重構(gòu),構(gòu)建更加結(jié)構(gòu)良好、靈活的SOA應(yīng)用。綜上所述,我們可以看到:
1使用IoC框架來實(shí)現(xiàn)對(duì)象的生命周期管理、配置管理和依賴管理,可以解除業(yè)務(wù)邏輯對(duì)服務(wù)調(diào)用的依賴關(guān)系;
2 使用AOP方法來解決Web Service調(diào)用中的crosscutting concerns,將為系統(tǒng)增加新的功能而不必更改應(yīng)用程序。
3通過IoC和AOP來屏蔽Web Service訪問的復(fù)雜性,使得開發(fā)人員可以更加專注于業(yè)務(wù)邏輯本身,也使得系統(tǒng)更加穩(wěn)定和富有彈性。 |