每日一得

          不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速開發(fā)
          最近關(guān)心的內(nèi)容:SSH,seam,flex,敏捷,TDD
          本站的官方站點(diǎn)是:顛覆軟件

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            220 隨筆 :: 9 文章 :: 421 評論 :: 0 Trackbacks

          級別: 初級

          易立, IBM 中國軟件開發(fā)實(shí)驗(yàn)室 SOA設(shè)計(jì)中心 高級軟件工程師
          趙勇, IBM 中國軟件開發(fā)實(shí)驗(yàn)室 SOA設(shè)計(jì)中心 高級軟件工程師

          2006 年 4 月 20 日

          在本文中,作者通過一個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)建一個靈活,可擴(kuò)展的IT基礎(chǔ)架構(gòu)來更好地支持隨需應(yīng)變的商務(wù)應(yīng)用。

          隨著SOA技術(shù)和產(chǎn)品的不斷成熟,現(xiàn)在越來越多的用戶開始了解并認(rèn)同SOA的理念,但對SOA項(xiàng)目的實(shí)施還缺乏信心。其主要原因是:SOA應(yīng)用開發(fā)還相對比較復(fù)雜。

          一年多來,本文作者所在的部門已經(jīng)從事了許多國內(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訪問解耦,為您提供一個更加靈活和易于擴(kuò)展的訪問模式。

          Spring是一個流行的輕量級容器,對IoC和AOP提供了良好的 支持。本文為您提供了一個基于Spring的實(shí)現(xiàn)供您下載學(xué)習(xí)。示例代碼工程使用Eclipse3.1/3.02和JDK1.4開發(fā), 您還需要Spring 1.2.5和Axis1.3提供的支持。詳細(xì)的下載信息請參見參考資源部分。





          回頁首


          2.Web Service調(diào)用

          Web Service是目前實(shí)現(xiàn)SOA應(yīng)用的一項(xiàng)基本的,適用的技術(shù),它為服務(wù)的訪問提供了一個被廣泛接受的開放標(biāo)準(zhǔn)。為了便于說明問題,我們將使用XMethods 網(wǎng)站(http://www.xmethods.net/)發(fā)布的貨幣兌換服務(wù)作為示例。并針對JAX-RPC 1.1,說明如何編寫Web Service 的調(diào)用代碼。

          2.1 示例說明

          http://xmethods.net 作為最早推出Web Service實(shí)際示例的網(wǎng)站,提供了很多優(yōu)秀的Web Service 樣例。其中有一個匯率計(jì)算服務(wù),可以返回兩個國家之間的貨幣兌換比例。獲取該服務(wù)的詳細(xì)信息,請參考該服務(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平臺的RPC服務(wù)調(diào)用標(biāo)準(zhǔn)接口,為Web Service客戶端調(diào)用提供了3種方法,分別是DII,動態(tài)代理,和靜態(tài)Stub。 DII(Dynamic Invocation Interface)采用直接調(diào)用方式,可以在程序中設(shè)置諸多的調(diào)用屬性,使用較為靈活,但是調(diào)用過程卻相對繁瑣復(fù)雜,易造成代碼膨脹且可重用性低,每次調(diào)用不同的Web Service都要重復(fù)進(jìn)行大量編碼。

          JAX-RPC中動態(tài)代理(Dynamic Proxy)的方法實(shí)現(xiàn)對Web Service的動態(tài)調(diào)用,可以在運(yùn)行時根據(jù)用戶定義的Client端接口創(chuàng)建適配對象。從而避免了直接操作底層的接口,減少了客戶端的冗余,屏蔽了調(diào)用相關(guān)的復(fù)雜性。

          使 用靜態(tài)Stub和Service Locator是目前最常用的調(diào)用方式。JAX-RPC使用靜態(tài)的Stub方式包裝對底層接口的調(diào)用,從而提供一種更為簡便的調(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個類:如圖 1 所示:


          圖 1: 客戶端代碼類圖
          圖 1: 客戶端代碼類圖

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

          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)接口,封裝了對Web Service訪問的底層邏輯。

          使用Stub調(diào)用Web Service的過程也非常簡單,讀者可以參考清單 1:


          清單 1:Web Service 調(diào)用代碼示例
          												
          														
          try {
          //創(chuàng)建ServiceLocator
          CurrencyExchangeServiceLocator locator = new
          CurrencyExchangeServiceLocator();
          //設(shè)定端點(diǎn)地址
          URL endPointAddress = new URL("http://services.xmethods.net:80/soap");
          //創(chuàng)建Stub實(shí)例
          CurrencyExchangePortType stub =
          locator.getCurrencyExchangePort(endPointAddress);
          //設(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訪問代碼雖然簡單但暴露出以下幾個問題:

          1.訪問Web Service所需的配置代碼被嵌入應(yīng)用邏輯之中
          在Web Service調(diào)用中,我們需要設(shè)定一系列必要的參數(shù)。比如:服務(wù)端點(diǎn)地址、用戶名/密碼、超時設(shè)定等等。這些參數(shù)在開發(fā)和運(yùn)行環(huán)境中都有可能發(fā)生變化。我們必須提供一種機(jī)制:在環(huán)境變化時,不必修改源代碼就可以改變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)行訪問。訪問方式對業(yè)務(wù)邏輯應(yīng)該是透明的。

          這 種分離客戶端代碼與服務(wù)訪問代碼的方式也有利于測試。這樣在開發(fā)過程中,負(fù)責(zé)集成的程序員就可能在遠(yuǎn)程服務(wù)還未完全實(shí)現(xiàn)的情況下,基于服務(wù)接口編寫集成代 碼,并通過編寫POJO(Plain Old Java Object)構(gòu)建偽服務(wù)實(shí)現(xiàn)來進(jìn)行單元測試和模擬運(yùn)行。這種開發(fā)方式對于保證分布式系統(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)用級別的異常類型。

          其類圖如圖 2 所示:


          圖 2:Business Delegate 模式的類圖圖解
          圖 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)的高度分離,降低對象之間的耦合程度。 Spring是一個非常流行的IoC容器,它通過配置文件來定義對象的生命周期和依賴關(guān)系,并提供了良好的配置管理能力。

          現(xiàn)在我們來重構(gòu)我們的Web Service應(yīng)用程序,我們首先為Business Delegate定義一個接口類型,它提供了一個應(yīng)用級組件接口,所有客戶端都應(yīng)通過它來執(zhí)行匯率計(jì)算,而不必關(guān)心實(shí)現(xiàn)細(xì)節(jié),如清單 2 所示:


          清單 2:接口定義的代碼示例
          												
          														

          Public interface CurrencyExchangeManager {
          //貨幣兌換計(jì)算
          //新價格 = 匯率 * 價格
          public float calculate(String country1, String country2, float price)
          throws CurrencyExchangeException;
          }

          Business Delegate的實(shí)現(xià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)建和配置對象,并定義它們的依賴關(guān)系。

          Spring 利用類工廠來創(chuàng)建和配置對象。在Spring框架中,已經(jīng)為基于JAX-RPC的Web Service調(diào)用提供了一個客戶端代理的類工廠實(shí)現(xiàn):JaxRpcPortProxyFactoryBean。在配置文件bean.xml中,我們將使 用JaxRpcPortProxyFactoryBean來創(chuàng)建和配置Web Service的客戶端代理"CurrencyExchangeService",如清單 5 所示。我們還將定義一個名為"CurrencyExchangeManager"的CurrencyExchangeManagerImpl實(shí)例,并建立 它與CurrencyExchangeService之間的依賴關(guān)系。有關(guān)Spring 配置和JaxRpcPortProxyFactoryBean的使用細(xì)節(jié)請參見參考資料。


          清單 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>

          最后我們創(chuàng)建一個測試程序來驗(yàn)證我們的代碼,如清單6 所示:


          清單 6:測試代碼
          												
          														

          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();
          }
          }
          }

          此時運(yùn)行測試客戶端,等待片刻將會看見測試結(jié)果,如清單 7 所示:


          清單 7:測試結(jié)果。
          												
          														
          12.34
          2853.26
          24.68

          注:該結(jié)果會隨著匯率的變化而出現(xiàn)不同的值。

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


          圖 3:示例程序的類圖
          圖 3:示例程序的類圖

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


          圖 4: 示例程序的順序圖
          圖 4:  示例程序的順序圖

          Spring 框架提供的ApplicationContext實(shí)現(xiàn)會根據(jù)配置文件中的描述信息來實(shí)現(xiàn)對象生命周期管理,配置管理以及依賴管理等功能。這一切對于應(yīng)用程 序是透明的,應(yīng)用程序代碼只依賴接口進(jìn)行編程,而無需考慮其它復(fù)雜問題。無論是Web Service的配置發(fā)生變化,或是改用不同的服務(wù)實(shí)現(xiàn)時,都不會對客戶端應(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需要使用者對WSDL中Port,Service,名空間等概念有深入的了解;而且如 果Web Service使用了復(fù)雜數(shù)據(jù)類型,開發(fā)人員需要手工定義類型映射代碼。所以JaxRpcPortProxyFactoryBean并不適合Web Service的初學(xué)者來使用。

          為了進(jìn)一步簡化Web Service代理的創(chuàng)建,并幫助讀者更好地理解類工廠在Spring框架下的作用。我們提供了一個基于靜態(tài)Stub的Web Service客戶端代理工廠實(shí)現(xià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ù)
          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.xmethods.net:80/soap</value>
          </property>
          <property name="timeout">
          <value>120000</value>
          </property>
          </bean>

          得益于Spring框架,雖然我們已經(jīng)替換了對象的類工廠,卻并不需要更改應(yīng)用代碼。通過Spring框架的IoC機(jī)制,我們可以完全使用面向接口的編程方式,而將實(shí)現(xiàn)的創(chuàng)建、配置和依賴管理交由Spring在運(yùn)行時完成。即使實(shí)現(xiàn)發(fā)生了變化,也不需要改變應(yīng)用程序結(jié)構(gòu)。





          回頁首


          4.新的思考

          故事并沒有結(jié)束,在開發(fā)過程中,我們又遇到了一系列關(guān)于Web Service調(diào)用的問題。

          4.1性能

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

          4.2緩存

          在 項(xiàng)目實(shí)踐中,一個有效的改善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ù):

          對 于Web Service應(yīng)用,系統(tǒng)的可用性也是一個需要考慮的重要問題。在運(yùn)行時由于網(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)該是可配置的,對應(yīng)用邏輯透明的。





          回頁首


          5.使用AOP解決SOA應(yīng)用中的Crosscutting Concern

          通過對上邊一系列問題的分析,讀者也許會發(fā)現(xiàn)這些問題并不是Web Service訪問的核心問題,但會影響系統(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)境作出隨時調(diào)整。

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

          • Joinpoint 是程序的運(yùn)行點(diǎn)。在Spring AOP中,一個Joinpoint對應(yīng)著一個方法調(diào)用。
          • Advice 定義了AOP框架在特定的Joinpoint的處理邏輯。Spring AOP框架通過interceptor方式實(shí)現(xiàn)了advice,并且提供了多種advice類型。其中最基本的"around advice"會在一個方法調(diào)用之前和之后被執(zhí)行。

          下面我們將利用Spring提供的MethodInterceptor來為Web Service調(diào)用實(shí)現(xiàn)我們的定義的處理邏輯。

          5.1 PerformanceMonitorInterceptor

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

          5.2 CacheInterceptor

          為 了不引入緩存策略的復(fù)雜性,我們只提供了一個利用HashMap的簡單實(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)中有一個生成Web Service調(diào)用主鍵的小技巧。因?yàn)閃eb Service引擎要求所有調(diào)用參數(shù)必須是可序列化的,所以我們可以利用Java提供的序列化功能來實(shí)現(xiàn)對象的克隆。如清單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;
          }
          //基于參數(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

          下面代碼提供了一個基于服務(wù)備份切換的故障恢復(fù)實(shí)現(xiàn),在運(yùn)行時,如果Interceptor檢測到服務(wù)調(diào)用由于網(wǎng)絡(luò)故障拋出異常時,它將使用備份服務(wù)的端點(diǎn)地址并重新調(diào)用。如清單11所示:


          清單 11: SimpleFailoverInterceptor的代碼示例
          												
          														

          public class SimpleFailoverInterceptor implements MethodInterceptor { …

          //實(shí)現(xiàn)支持端點(diǎn)運(yùn)行時切換的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ù)切換的功能,我們在WebServicePortProxyFactoryBean中為填加了配置參數(shù)"endpointAddress2",它會在創(chuàng)建的Web Service客戶端代理對象中記錄備份URL。

          我 們可以在CurrencyExchangeService加入下列參數(shù)來試驗(yàn)SimpleFailoverInterceptor的功能。其中第一個端點(diǎn) 地址為一個錯誤的URL。在第一次調(diào)用服務(wù)時,SimpleFailoverInterceptor會偵測到網(wǎng)絡(luò)故障的發(fā)生,并自動切換使用第二個端點(diǎn)地 址繼續(xù)訪問。如清單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配置文件和運(yùn)行結(jié)果

          現(xiàn) 在我們需要在Spring配置文件中,為所有interceptor添加定義,并描述如何為CurrencyExchangeService構(gòu)建AOP Proxy。需要指出的是,我們要在interceptorName列表中聲明interceptor鏈的調(diào)用順序,還要將原有 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創(chuàng)建了一個AOP代理,并且建立了一個Interceptor鏈。這樣在調(diào)用Web Service時,Spring框架會依次調(diào)用Interceptor執(zhí)行。實(shí)例執(zhí)行的順序圖將如圖5所示:


          圖5系統(tǒng)運(yùn)行順序圖
          圖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請求/響應(yīng)過程中,Web Service 引擎會根據(jù)部署描述中的定義,按照一定的次序調(diào)用Handler的處理代碼。用戶編寫的Handler實(shí)現(xiàn)可以截獲并修改Web Service消息和處理流程,從而實(shí)現(xiàn)對Web Service引擎處理行為的定制和增強(qiáng)。

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

          從 此可以看出,JAX-RPC Handler與我們在上文中所提供的AOP Interceptor都可以幫助我們的SOA應(yīng)用程序?qū)崿F(xiàn)關(guān)注分離(Separate Concern)的目標(biāo),在不改變應(yīng)用代碼的同時,增強(qiáng)或改變Web Service服務(wù)訪問的功能。雖然我們可以利用它們實(shí)現(xiàn)一些類似的功能,但它們具有著不同的特點(diǎn)和適用范圍。

          JAX-RPC Handler是Web Service引擎的擴(kuò)展機(jī)制。如果我們需要實(shí)現(xiàn)對SOAP消息進(jìn)行的修改和處理,加入自定義的SOAP Header或?qū)ο?nèi)容進(jìn)行加密,Handler是我們的最佳選擇。而AOP是針對對象級別的擴(kuò)展機(jī)制,它更適合對應(yīng)用層邏輯進(jìn)行操作。

          比 如,我們在上文展示的利用AOP實(shí)現(xiàn)的CacheInterceptor,它緩存的是Web Service調(diào)用參數(shù)和結(jié)果。而我們也可以通過JAX-RPC Handler實(shí)現(xiàn)一個面向SOAP消息的實(shí)現(xiàn),它將緩存Web Service的請求消息和響應(yīng)消息。這兩個實(shí)現(xiàn)相比,基于AOP的實(shí)現(xiàn)更加簡單、直觀、快速、對資源消耗也比較小。而面向SOAP消息的實(shí)現(xiàn)則更加靈 活,對于不采用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)行處理。

          在本文中,我們通過一個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)對象的生命周期管理、配置管理和依賴管理,可以解除業(yè)務(wù)邏輯對服務(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)定和富有彈性。






          回頁首


          下載

          描述 名字 大小 下載方法
          code sample code.zip 27 KB HTTP
          關(guān)于下載方法的信息 Get Adobe? Reader?




          回頁首


          參考資料





          回頁首


          作者簡介


          易立 IBM 中國軟件開發(fā)實(shí)驗(yàn)室 SOA設(shè)計(jì)中心 高級軟件工程師。



          趙勇 IBM 中國軟件開發(fā)實(shí)驗(yàn)室 SOA設(shè)計(jì)中心 軟件工程師。

          posted on 2006-07-28 13:29 Alex 閱讀(486) 評論(1)  編輯  收藏 所屬分類: SOA

          評論

          # re: [轉(zhuǎn)]SOA實(shí)踐 -- 使用IoC和AOP重構(gòu)SOA應(yīng)用 2007-10-15 13:33 DSD
          在靜態(tài)工廠那這個方法沒有定義preparePortStub方法,下面怎么能使用呢?  回復(fù)  更多評論
            

          主站蜘蛛池模板: 满洲里市| 古丈县| 镇原县| 忻城县| 泾源县| 宁波市| 郸城县| 余庆县| 遂平县| 巴林左旗| 新宁县| 论坛| 南阳市| 平定县| 茶陵县| 平凉市| 阳原县| 阳西县| 体育| 科技| 绍兴县| 永德县| 通渭县| 泽库县| 榆林市| 长岭县| 克拉玛依市| 海伦市| 怀远县| 北票市| 汾西县| 安平县| 汉阴县| 盐源县| 开平市| 木兰县| 色达县| 舒兰市| 湘潭市| 广汉市| 南投市|