了解AOP(一)
原文:http://www.javaworld.com/javaworld/jw-01-2002/jw-0118-aspect.html了解AOP(第一部分)--用面向方面的編程方式分離軟件關(guān)注點(diǎn)
摘要
多數(shù)軟件系統(tǒng)都包含幾個(gè)跨越多個(gè)模塊的關(guān)注點(diǎn)。用面向?qū)ο蠹夹g(shù)實(shí)現(xiàn)這些關(guān)注點(diǎn)會(huì)使系統(tǒng)難以實(shí)現(xiàn),難以理解,并且不利于軟件的演進(jìn)。新的AOP(面向角度的編程方法)利用模塊化來(lái)分離軟件中橫切多模塊的關(guān)注點(diǎn)。使用AOP,你可以建立容易設(shè)計(jì),易于理解和維護(hù)的系統(tǒng)。此外,AOP可以帶來(lái)更高的產(chǎn)出,更好的質(zhì)量,更好的擴(kuò)展性,這篇文章是這個(gè)系列里三篇文章中的第一章,介紹AOP的概念和它所解決的問(wèn)題。
作者:Ramnivas Laddad
一個(gè)關(guān)注點(diǎn)就是一個(gè)特定的目的、一塊我們感興趣的的區(qū)域。從技術(shù)的角度來(lái)說(shuō),一個(gè)典型的軟件系統(tǒng)包含一些核心的關(guān)注點(diǎn)和系統(tǒng)級(jí)的關(guān)注點(diǎn)。舉個(gè)例子來(lái)說(shuō),一個(gè)信用卡處理系統(tǒng)的核心關(guān)注點(diǎn)是借貸/存入處理,而系統(tǒng)級(jí)的關(guān)注點(diǎn)則是日志,事務(wù)完整性,授權(quán),安全性及性能問(wèn)題等,許多關(guān)注點(diǎn)——我們叫它橫切關(guān)注點(diǎn)——會(huì)在多個(gè)模塊中出現(xiàn),使用現(xiàn)有的編程方法,橫切關(guān)注點(diǎn)會(huì)橫越多個(gè)模塊,結(jié)果是使系統(tǒng)難以設(shè)計(jì)、理解、實(shí)現(xiàn)和演進(jìn)。
AOP(面向角度的編程方式)能夠比上述方法更好的分離系統(tǒng)關(guān)注點(diǎn),從而提供模塊化的橫切關(guān)注點(diǎn)。
在這篇文章里——關(guān)于AOP的三篇文章的第一章,我首先會(huì) 解釋橫切關(guān)注點(diǎn)在一些即使是中等復(fù)雜度的軟件系統(tǒng)中也會(huì)引起的問(wèn)題,接著我會(huì)介紹AOP的核心概念并演示AOP是怎樣解決橫切關(guān)注點(diǎn)問(wèn)題的。
軟件編程方法的演進(jìn)
在計(jì)算機(jī)科學(xué)的早期階段,開(kāi)發(fā)人員使用直接的機(jī)器級(jí)代碼來(lái)編程,不幸的是,程序員得花費(fèi)更多時(shí)間來(lái)考慮一種特定機(jī)器的指令集而不是手中需要解決的問(wèn)題本身。慢慢的我們轉(zhuǎn)而使用允許對(duì)底層機(jī)器做某種抽象的高級(jí)語(yǔ)言。然后是結(jié)構(gòu)化語(yǔ)言,我們可以把問(wèn)題分解成一些必要的過(guò)程來(lái)完成任務(wù)。但是,隨著復(fù)雜程度的增加,我們又需要更適合的技術(shù)。面向?qū)ο蟮木幊谭绞剑∣OP)使我們可以把系統(tǒng)看作是一批相互合作的對(duì)象。類允許我們把實(shí)現(xiàn)細(xì)節(jié)隱藏在接口下。多態(tài)性為相關(guān)概念提供公共的行為和接口,并允許特定的組件在無(wú)需訪問(wèn)基礎(chǔ)實(shí)現(xiàn)的前提下改變特定行為。
編程方法和語(yǔ)言決定了我們和計(jì)算機(jī)交流的方式。每一種新的方法學(xué)都提出一種新的分解問(wèn)題的方法:機(jī)器碼、偽代碼、過(guò)程和類等。每種新的方法學(xué)都使得從系統(tǒng)需求到編程概念的映射更加自然。編程方法學(xué)的發(fā)展讓我們可以建立更加復(fù)雜的系統(tǒng),這句話反過(guò)來(lái)說(shuō)也對(duì),我們能夠建立更加復(fù)雜的系統(tǒng)是因?yàn)檫@些技術(shù)允許我們處理這種復(fù)雜度。
現(xiàn)在,大多數(shù)軟件項(xiàng)目都選擇OOP的編程方式。確實(shí),OOP已經(jīng)表明了它處理一般行為的能力,但是,我們一會(huì)兒會(huì)看到(或許你已經(jīng)感覺(jué)到了), OOP不能很好的處理橫越多個(gè)——經(jīng)常是不相關(guān)的——模塊的行為,相比之下,AOP填補(bǔ)了這個(gè)空白,它很可能會(huì)是編程方法學(xué)發(fā)展的下一個(gè)里程碑。
把系統(tǒng)看作一批關(guān)注點(diǎn)
我們可以把一個(gè)復(fù)雜的系統(tǒng)看作是由多個(gè)關(guān)注點(diǎn)來(lái)組合實(shí)現(xiàn)的,一個(gè)典型的系統(tǒng)可能會(huì)包括幾個(gè)方面的關(guān)注點(diǎn),如業(yè)務(wù)邏輯,性能,數(shù)據(jù)存儲(chǔ),日志和調(diào)試信息,授權(quán),安全,線程,錯(cuò)誤檢查等,還有開(kāi)發(fā)過(guò)程中的關(guān)注點(diǎn),如易懂,易維護(hù),易追查,易擴(kuò)展等,圖一演示了由不同模塊實(shí)現(xiàn)的一批關(guān)注點(diǎn)組成了一個(gè)系統(tǒng)。

圖 1. 把模塊作為一批關(guān)注點(diǎn)來(lái)實(shí)現(xiàn)
圖二把需求比作一束穿過(guò)三棱鏡的光,我們讓需求之光通過(guò)關(guān)注點(diǎn)鑒別三棱鏡,就會(huì)區(qū)別出每個(gè)關(guān)注點(diǎn),同樣的方法也適用于開(kāi)發(fā)階段的關(guān)注點(diǎn)。

圖 2. 關(guān)注點(diǎn)分解: 三棱鏡法則
開(kāi)發(fā)人員建立一個(gè)系統(tǒng)以滿足多個(gè)需求,我們可以大致的把這些需求分類為核心模塊級(jí)需求和系統(tǒng)級(jí)需求。很多系統(tǒng)級(jí)需求一般來(lái)說(shuō)是相互獨(dú)立的,但它們一般都會(huì)橫切許多核心模塊。舉個(gè)例子來(lái)說(shuō),一個(gè)典型的企業(yè)應(yīng)用包含許多橫切關(guān)注點(diǎn),如驗(yàn)證,日志,資源池,系統(tǒng)管理,性能及存儲(chǔ)管理等,每一個(gè)關(guān)注點(diǎn)都牽涉到幾個(gè)子系統(tǒng),如存儲(chǔ)管理關(guān)注點(diǎn)會(huì)影響到所有的有狀態(tài)業(yè)務(wù)對(duì)象。
讓我們來(lái)看一個(gè)簡(jiǎn)單,但是具體的例子,考慮一個(gè)封裝了業(yè)務(wù)邏輯的類的實(shí)現(xiàn)框架:
代碼: |
public class SomeBusinessClass extends OtherBusinessClass { ? ? // 核心數(shù)據(jù)成員 ? ? // 其它數(shù)據(jù)成員:日志流,保證數(shù)據(jù)完整性的標(biāo)志位等 ? ? // 重載基類的方法 ? ? public void performSomeOperation(OperationInformation info) { ? ? ? ? // 安全性驗(yàn)證 ? ? ? ? // 檢查傳入數(shù)據(jù)是否滿足協(xié)議 ? ? ? ? // 鎖定對(duì)象以保證當(dāng)其他線程訪問(wèn)時(shí)的數(shù)據(jù)完整性 ? ? ? ? // 檢查緩存中是否為最新信息 ? ? ? ? // 紀(jì)錄操作開(kāi)始執(zhí)行時(shí)間 ? ? ? ? // 執(zhí)行核心操作 ? ? ? ? // 紀(jì)錄操作完成時(shí)間 ? ? ? ? // 給對(duì)象解鎖 ? ? } ? ? // 一些類似操作 ? ? public void save(PersitanceStorage ps) { ? ? } ? ? public void load(PersitanceStorage ps) { ? ? } } |
在上面的代碼中,我們注意到三個(gè)問(wèn)題,首先,其它數(shù)據(jù)成員不是這個(gè)類的核心關(guān)注點(diǎn),第二,performSomeOperation()的實(shí)現(xiàn)做了許多核心操作之外的事,它要處理日志,驗(yàn)證,線程安全,協(xié)議驗(yàn)證和緩存管理等一些外圍操作,而且這些外圍操作同樣也會(huì)應(yīng)用于其他類,第三,save() 和load()執(zhí)行的持久化操作是否構(gòu)成這個(gè)類的核心清楚的。
橫切關(guān)注點(diǎn)的問(wèn)題
雖然橫切關(guān)注點(diǎn)會(huì)跨越多個(gè)模塊,但當(dāng)前的技術(shù)傾向于使用一維的方法學(xué)來(lái)處理這種需求,把對(duì)應(yīng)需求的實(shí)現(xiàn)強(qiáng)行限制在一維的空間里。這個(gè)一維空間就是核心模塊級(jí)實(shí)現(xiàn),其他需求的實(shí)現(xiàn)被嵌入在這個(gè)占統(tǒng)治地位的空間,換句話說(shuō),需求空間是一個(gè)n維空間,而實(shí)現(xiàn)空間是一維空間,這種不匹配導(dǎo)致了糟糕的需求到實(shí)現(xiàn)的映射
表現(xiàn)
用當(dāng)前方法學(xué)實(shí)現(xiàn)橫切關(guān)注點(diǎn)是不好的,它會(huì)帶來(lái)一些問(wèn)題,我們可以大致把這些問(wèn)題分為兩類:
- 代碼混亂:軟件系統(tǒng)中的模塊可能要同時(shí)兼顧幾個(gè)方面的需要。舉例來(lái)說(shuō),開(kāi)發(fā)者經(jīng)常要同時(shí)考慮業(yè)務(wù)邏輯,性能,同步,日志和安全等問(wèn)題,兼顧各方面的需要導(dǎo)致相應(yīng)關(guān)注點(diǎn)的實(shí)現(xiàn)元素同時(shí)出現(xiàn),引起代碼混亂。
- 代碼分散:由于橫切關(guān)注點(diǎn),本來(lái)就涉及到多個(gè)模塊,相關(guān)實(shí)現(xiàn)也就得遍布在這些模塊里,如在一個(gè)使用了數(shù)據(jù)庫(kù)的系統(tǒng)里,性能問(wèn)題就會(huì)影響所有訪問(wèn)數(shù)據(jù)庫(kù)的模塊。這導(dǎo)致代碼分散在各處
結(jié)果
混亂和分散的代碼會(huì)在多個(gè)方面影響系統(tǒng)的設(shè)計(jì)和開(kāi)發(fā):
- 可讀性差:同時(shí)實(shí)現(xiàn)幾個(gè)關(guān)注點(diǎn)模糊了不同關(guān)注點(diǎn)的實(shí)現(xiàn),使得關(guān)注點(diǎn)與其實(shí)現(xiàn)之間的對(duì)應(yīng)關(guān)系不明顯。
- 低產(chǎn)出:同時(shí)實(shí)現(xiàn)幾個(gè)關(guān)注點(diǎn)把開(kāi)發(fā)人員的注意力從主要的轉(zhuǎn)移到外圍關(guān)注點(diǎn),導(dǎo)致產(chǎn)能降低。
- 低代碼重用率:由于這種情況下,一個(gè)模塊實(shí)現(xiàn)多個(gè)關(guān)注點(diǎn),其他需要類似功能的系統(tǒng)不能馬上使用該模塊,進(jìn)一步降低了產(chǎn)能。
- 代碼質(zhì)量差:混亂的代碼掩蓋了代碼中隱藏的問(wèn)題。而且,由于同時(shí)要處理多個(gè)關(guān)注點(diǎn),應(yīng)該特別注意的關(guān)注點(diǎn)得不到應(yīng)有的關(guān)注
- 難以擴(kuò)展:狹窄的視角和有限的資源總是產(chǎn)生僅注意當(dāng)前關(guān)注點(diǎn)的設(shè)計(jì)。新的需求導(dǎo)致從新實(shí)現(xiàn)。由于實(shí)現(xiàn)不是模塊化的,就是說(shuō)實(shí)現(xiàn)牽涉到多個(gè)模塊,為了新需求修改子系統(tǒng)可能會(huì)帶來(lái)數(shù)據(jù)的不一致,而且還需相當(dāng)規(guī)模測(cè)試來(lái)保證這些修改不會(huì)帶來(lái)bug。
當(dāng)前解決方法
由于多數(shù)系統(tǒng)中都包含橫切關(guān)注點(diǎn),自然的已經(jīng)形成了一些技術(shù)來(lái)模塊化橫切關(guān)注點(diǎn)的實(shí)現(xiàn),這些技術(shù)包括:混入類,設(shè)計(jì)模式和面向特定問(wèn)題域的解決方式
使用混入類,你可以推遲關(guān)注點(diǎn)的最終實(shí)現(xiàn)。基本類包含一個(gè)混入類的實(shí)例,允許系統(tǒng)的其他部分設(shè)置這個(gè)實(shí)例,舉個(gè)例子來(lái)說(shuō),實(shí)現(xiàn)業(yè)務(wù)邏輯的類包含一個(gè)混入的logger,系統(tǒng)的其他部分可以設(shè)置這個(gè)logger已得到合適的日志類型,比如logger可能被設(shè)置為使用文件系統(tǒng)或是消息中間件.在這種方式下,雖然日志的具體實(shí)現(xiàn)被推遲啦,基本類還是得包含在所有的寫(xiě)日志的點(diǎn)調(diào)用日志操作和控制日志信息的代碼。
行為型設(shè)計(jì)模式,如Visitor和Template模式,也允許你推遲具體實(shí)現(xiàn)。但是也就像混入類一樣,操作的控制——調(diào)用visitor或template的邏輯——仍然留給了基本類
面向特定問(wèn)題域的解決方式,如框架和應(yīng)用服務(wù)器,允許開(kāi)發(fā)者用更模塊化的方式處理某些橫切關(guān)注點(diǎn)。比如EJB(Enterprise JavaBean,企業(yè)級(jí)javabean)架構(gòu),可以處理安全,系統(tǒng)管理,性能和容器管理的持久化(container-managed persistence)等橫切關(guān)注點(diǎn)。Bean的開(kāi)發(fā)者僅需關(guān)心業(yè)務(wù)邏輯,而部署者僅需關(guān)心部署問(wèn)題,如bean與數(shù)據(jù)庫(kù)的映射。但是大多數(shù)情況下,開(kāi)發(fā)者還是要了解存儲(chǔ)結(jié)構(gòu)。這種方式下,你用基于XML的映射關(guān)系描述器來(lái)實(shí)現(xiàn)于數(shù)據(jù)持久化相關(guān)的橫切關(guān)注點(diǎn)。
面向特定問(wèn)題域的解決方式提供了解決特定問(wèn)題的專門(mén)機(jī)制,它的缺點(diǎn)是對(duì)于每一種這樣的解決方式開(kāi)發(fā)人員都必須重新學(xué)習(xí),另外,由于這種方式是特定問(wèn)題域相關(guān)的,屬于特定問(wèn)題域之外的橫切關(guān)注點(diǎn)需要特殊的對(duì)待
設(shè)計(jì)師的兩難局面
好的系統(tǒng)設(shè)計(jì)師不僅會(huì)考慮當(dāng)前需求,還會(huì)考慮到可能會(huì)有的需求以避免到處打補(bǔ)丁。這樣就存在一個(gè)問(wèn)題,預(yù)知將來(lái)是很困難的,如果你漏過(guò)了將來(lái)可能會(huì)有的橫切關(guān)注點(diǎn)的需求,你將會(huì)需要修改或甚至是重新實(shí)現(xiàn)系統(tǒng)的許多部分;從另一個(gè)角度來(lái)說(shuō),太過(guò)于關(guān)注不一定需要的需求會(huì)導(dǎo)致過(guò)分設(shè)計(jì)(overdesigned)的,難以理解的,臃腫的系統(tǒng)。所以系統(tǒng)設(shè)計(jì)師處在這么一個(gè)兩難局面中:怎么設(shè)計(jì)算是過(guò)分設(shè)計(jì)?應(yīng)該寧可設(shè)計(jì)不足還是寧可過(guò)分設(shè)計(jì)?
舉個(gè)例子來(lái)說(shuō),設(shè)計(jì)師是否應(yīng)該在系統(tǒng)中包含現(xiàn)在并不需要的日志機(jī)制?如果是的話,哪里是應(yīng)該寫(xiě)日志的點(diǎn)?日志應(yīng)該記錄那些信息?相似的例子還有關(guān)于性能的優(yōu)化問(wèn)題,我們很少能預(yù)先知道瓶頸的所在。常用的方法是建立系統(tǒng),profile它,然后翻新系統(tǒng)以提高性能,這種方式可能會(huì)依照 profiling修改系統(tǒng)的很多部分,此外,隨著時(shí)間的流逝,由于使用方式的變化,可能還會(huì)產(chǎn)生新的瓶頸,類庫(kù)設(shè)計(jì)師的任務(wù)更困難,因?yàn)樗茈y設(shè)想出所有對(duì)類庫(kù)的使用方式。
總而言之,設(shè)計(jì)師很難顧及到系統(tǒng)可能需要處理的所有關(guān)注點(diǎn)。即使是在已經(jīng)知道了需求的前提下,某些建立系統(tǒng)時(shí)需要的細(xì)節(jié)也可能不能全部得到。整體設(shè)計(jì)就面臨著設(shè)計(jì)不足/過(guò)分設(shè)計(jì)的兩難局面。
AOP基礎(chǔ)
到目前為止的討論說(shuō)明模塊化橫切關(guān)注點(diǎn)是有好處的。研究人員已經(jīng)嘗試了多種方法來(lái)實(shí)現(xiàn)這個(gè)任務(wù),這些方法有一個(gè)共同的主題:分離關(guān)注點(diǎn)。AOP是這些方法中的一種,它的目的是清晰的分離關(guān)注點(diǎn)來(lái)解決以上提到的問(wèn)題。
AOP,從其本質(zhì)上講,使你可以用一種松散耦合的方式來(lái)實(shí)現(xiàn)獨(dú)立的關(guān)注點(diǎn),然后,組合這些實(shí)現(xiàn)來(lái)建立最終系統(tǒng)。用它所建立的系統(tǒng)是使用松散耦合的,模塊化實(shí)現(xiàn)的橫切關(guān)注點(diǎn)來(lái)搭建的。與之對(duì)照,用OOP建立的系統(tǒng)則是用松散耦合的模塊化實(shí)現(xiàn)的一般關(guān)注點(diǎn)來(lái)實(shí)現(xiàn)的。在AOP終,這些模塊化單元叫方面(aspect),而在OOP中,這些一般關(guān)注點(diǎn)的實(shí)現(xiàn)單元叫做類。
AOP包括三個(gè)清晰的開(kāi)發(fā)步驟:- 方面分解:分解需求提取出橫切關(guān)注點(diǎn)和一般關(guān)注點(diǎn)。在這一步里,你把核心模塊級(jí)關(guān)注點(diǎn)和系統(tǒng)級(jí)的橫切關(guān)注點(diǎn)分離開(kāi)來(lái)。就前面所提到的信用卡例子來(lái)說(shuō),你可以分解出三個(gè)關(guān)注點(diǎn):核心的信用卡處理,日志和驗(yàn)證。
- 關(guān)注點(diǎn)實(shí)現(xiàn):各自獨(dú)立的實(shí)現(xiàn)這些關(guān)注點(diǎn),還用上面信用卡的例子,你要實(shí)現(xiàn)信用卡處理單元,日志單元和驗(yàn)證單元。
- 方面的重新組合:在這一步里,方面集成器通過(guò)創(chuàng)建一個(gè)模塊單元——方面來(lái)指定重組的規(guī)則。重組過(guò)程——也叫織入或結(jié)合——?jiǎng)t使用這些信息來(lái)構(gòu)建最終系統(tǒng),還拿信用卡的那個(gè)例子,你可以指定(用某種AOP的實(shí)現(xiàn)所提供的語(yǔ)言)每個(gè)操作的開(kāi)始和結(jié)束需要紀(jì)錄,并且每個(gè)操作在涉及到業(yè)務(wù)邏輯之前必須通過(guò)驗(yàn)證。
圖 3. AOP 開(kāi)發(fā)的步驟
AOP與OOP的不同關(guān)鍵在于它處理橫切關(guān)注點(diǎn)的方式,在AOP中,每個(gè)關(guān)注點(diǎn)的實(shí)現(xiàn)都不知道其它關(guān)注點(diǎn)是否會(huì)‘關(guān)注’它,如信用卡處理模塊并不知道其它的關(guān)注點(diǎn)實(shí)現(xiàn)正在為它做日志和驗(yàn)證操作。它展示了一個(gè)從OOP轉(zhuǎn)化來(lái)的強(qiáng)大的開(kāi)發(fā)范型。
注意:一個(gè)AOP實(shí)現(xiàn)可以借助其它編程范型作為它的基礎(chǔ),從而原封不動(dòng)的保留其基礎(chǔ)范型的優(yōu)點(diǎn)。例如,AOP可以選擇OOP作為它的基礎(chǔ)范型,從而把OOP善于處理一般關(guān)注點(diǎn)的好處直接帶過(guò)來(lái)。用這樣一種實(shí)現(xiàn),獨(dú)立的一般關(guān)注點(diǎn)可以使用OOP技術(shù)。這就像過(guò)程型語(yǔ)言是許多OOP語(yǔ)言的基礎(chǔ)一樣。
織入舉例
織入器——一個(gè)處理器——組裝一個(gè)個(gè)關(guān)注點(diǎn)(這個(gè)過(guò)程叫做織入)。就是說(shuō),它依照提供給它的規(guī)則把不同的執(zhí)行邏輯段混編起來(lái)。
為了說(shuō)明代碼織入,讓我們回到信用卡處理的例子,為了簡(jiǎn)單起見(jiàn),我們只考慮兩個(gè)操作:存入和取出,并且我們假設(shè)已經(jīng)有了一個(gè)合適的logger.
來(lái)看一下下面的信用卡模塊:代碼:
public class CreditCardProcessor {
? ? public void debit(CreditCard card, Currency amount)
? ? ? ?throws InvalidCardException, NotEnoughAmountException,
? ? ? ? ? ? ? CardExpiredException {
? ? ? ? // 取出邏輯
? ? }
? ?
? ? public void credit(CreditCard card, Currency amount)
? ? ? ? throws InvalidCardException {
? ? ? ? // 存入邏輯
? ? }
}
下面是日志接口代碼:
public interface Logger {
? ? public void log(String message);
}
所需組合需要如下織入規(guī)則,這里用自然語(yǔ)言來(lái)表達(dá)(本文的后面會(huì)提供這些織入規(guī)則的程序版本):
[list=a] - 方面分解:分解需求提取出橫切關(guān)注點(diǎn)和一般關(guān)注點(diǎn)。在這一步里,你把核心模塊級(jí)關(guān)注點(diǎn)和系統(tǒng)級(jí)的橫切關(guān)注點(diǎn)分離開(kāi)來(lái)。就前面所提到的信用卡例子來(lái)說(shuō),你可以分解出三個(gè)關(guān)注點(diǎn):核心的信用卡處理,日志和驗(yàn)證。
- 紀(jì)錄每個(gè)公共操作的開(kāi)始
- 紀(jì)錄每個(gè)公共操作的結(jié)束
- 紀(jì)錄所有公共方法拋出的異常
織入器就會(huì)使用這些織入規(guī)則和關(guān)注點(diǎn)實(shí)現(xiàn)來(lái)產(chǎn)生與如下代碼有相同效果的代碼:
代碼: |
public class CreditCardProcessorWithLogging { ? ? Logger _logger; ? ? public void debit(CreditCard card, Money amount) ? ? ? ? throws InvalidCardException, NotEnoughAmountException, ? ? ? ? ? ? ? ?CardExpiredException { ? ? ? ? _logger.log("Starting CreditCardProcessor.credit(CreditCard, Money) " ? ? ? ? ? ? ? ? ? ? + "Card: " + card + " Amount: " + amount); ? ? ? ? // 取出邏輯 ? ? ? ? _logger.log("Completing CreditCardProcessor.credit(CreditCard, Money) " ? ? ? ? ? ? ? ? ? ? + "Card: " + card + " Amount: " + amount); ? ? } ? ? ? ? public void credit(CreditCard card, Money amount) ? ? ? ? throws InvalidCardException { ? ? ? ? System.out.println("Debiting"); ? ? ? ? _logger.log("Starting CreditCardProcessor.debit(CreditCard, Money) " ? ? ? ? ? ? ? ? ? ? + "Card: " + card + " Amount: " + amount); ? ? ? ? // 存入邏輯 ? ? ? ? _logger.log("Completing CreditCardProcessor.credit(CreditCard, Money) " ? ? ? ? ? ? ? ? ? ? + "Card: " + card + " Amount: " + amount); ? ? } } |
AOP語(yǔ)言剖析
就像其他編程范型的實(shí)現(xiàn)一樣,AOP的實(shí)現(xiàn)有兩部分組成:語(yǔ)言規(guī)范和實(shí)現(xiàn)。語(yǔ)言規(guī)范描述了語(yǔ)言的基礎(chǔ)單元和語(yǔ)法。語(yǔ)言實(shí)現(xiàn)則按照語(yǔ)言規(guī)范來(lái)驗(yàn)證代碼的正確性并把代碼轉(zhuǎn)成目標(biāo)機(jī)器的可執(zhí)行形式。這一節(jié),我來(lái)解釋一下AOP組成部分。
AOP語(yǔ)言規(guī)范
從抽象的角度看來(lái),一種AOP語(yǔ)言要說(shuō)明下面兩個(gè)方面:
- 關(guān)注點(diǎn)的實(shí)現(xiàn):把每個(gè)需求映射為代碼,然后,編譯器把它翻譯成可執(zhí)行代碼,由于關(guān)注點(diǎn)的實(shí)現(xiàn)以指定過(guò)程的形式出現(xiàn),你可以使用傳統(tǒng)語(yǔ)言如C,C++,Java等。
- 織入規(guī)則規(guī)范:怎樣把獨(dú)立實(shí)現(xiàn)的關(guān)注點(diǎn)組合起來(lái)形成最終系統(tǒng)呢?為了這個(gè)目的,需要建立一種語(yǔ)言來(lái)指定組合不同的實(shí)現(xiàn)單元以形成最終系統(tǒng)的規(guī)則,這種指定織入規(guī)則的語(yǔ)言可以是實(shí)現(xiàn)語(yǔ)言的擴(kuò)展,也可以是一種完全不同的語(yǔ)言。
AOP語(yǔ)言的實(shí)現(xiàn)
AOP的編譯器執(zhí)行兩步操作:
- 組裝關(guān)注點(diǎn)。
- 把組裝結(jié)果轉(zhuǎn)成可執(zhí)行代碼
AOP實(shí)現(xiàn)可以用多種方式實(shí)現(xiàn)織入,包括源碼到源碼的轉(zhuǎn)換。它預(yù)處理每個(gè)方面的源碼產(chǎn)生織入過(guò)的源碼,然后把織入過(guò)的源碼交給基礎(chǔ)語(yǔ)言的編譯器產(chǎn)生最終可執(zhí)行代碼。比如,使用這種方式,一個(gè)基于Java的AOP實(shí)現(xiàn)可以先把不同的方面轉(zhuǎn)化成Java源代碼,然后讓Java編譯器把它轉(zhuǎn)化成字節(jié)碼。也可以直接在字節(jié)碼級(jí)別執(zhí)行織入;畢竟,字節(jié)碼本身也是一種源碼。此外,下面的執(zhí)行系統(tǒng)——Java虛擬機(jī)——也可以是方面認(rèn)知的,基于Java的AOP 實(shí)現(xiàn)如果使用這種方式的話,虛擬機(jī)可以先裝入織入規(guī)則,然后對(duì)后來(lái)裝入的類都應(yīng)用這種規(guī)則,也就是說(shuō),它可以執(zhí)行just-in-time的方面織入。
AOP的好處
AOP可幫助我們解決上面提到的代碼混亂和代碼分散所帶來(lái)的問(wèn)題,它還有一些別的好處:
- 塊化橫切關(guān)注點(diǎn):AOP用最小的耦合處理每個(gè)關(guān)注點(diǎn),使得即使是橫切關(guān)注點(diǎn)也是模塊化的。這樣的實(shí)現(xiàn)產(chǎn)生的系統(tǒng),其代碼的冗余小。模塊化的實(shí)現(xiàn)還使得系統(tǒng)容易理解和維護(hù)
- 系統(tǒng)容易擴(kuò)展:由于方面模塊根本不知道橫切關(guān)注點(diǎn),所以很容易通過(guò)建立新的方面加入新的功能,另外,當(dāng)你往系統(tǒng)中加入新的模塊時(shí),已有的方面自動(dòng)的橫切進(jìn)來(lái),使系統(tǒng)的易于擴(kuò)展
- 設(shè)計(jì)決定的遲綁定:還記得設(shè)計(jì)師的兩難局面嗎?使用AOP,設(shè)計(jì)師可以推遲為將來(lái)的需求作決定,因?yàn)樗梢园堰@種需求作為獨(dú)立的方面很容易的實(shí)現(xiàn)。
- 更好的代碼重用性:由于AOP把每個(gè)方面實(shí)現(xiàn)為獨(dú)立的模塊,模塊之間是松散耦合的,舉例來(lái)說(shuō),你可以用另外一個(gè)獨(dú)立的日志寫(xiě)入器方面(替換當(dāng)前的)把日志寫(xiě)入數(shù)據(jù)庫(kù),以滿足不同的日志寫(xiě)入要求。
總的來(lái)說(shuō),松散耦合的實(shí)現(xiàn)意味著更好的代碼重用性, AOP在使系統(tǒng)實(shí)現(xiàn)松散耦合這一點(diǎn)上比OOP做得更好。
AspectJ:一個(gè)Java的AOP實(shí)現(xiàn)
AspectJ是一個(gè)可免費(fèi)獲得的由施樂(lè)公司帕洛阿爾托研究中心(Xerox PARC)開(kāi)發(fā)Java的AOP實(shí)現(xiàn),它是一個(gè)多功能的面向方面的Java擴(kuò)展。它使用Java作為單個(gè)關(guān)注點(diǎn)的實(shí)現(xiàn)語(yǔ)言,并擴(kuò)展Java以指定織入規(guī)則。這些規(guī)則是用切入點(diǎn)(pointcuts)、聯(lián)結(jié)點(diǎn)(join points),通知(advice)和方面(aspect)來(lái)說(shuō)明的。聯(lián)結(jié)點(diǎn)是定義在程序執(zhí)行過(guò)程之間的點(diǎn),切入點(diǎn)由用來(lái)指定聯(lián)結(jié)點(diǎn)的語(yǔ)言構(gòu)造,通知定義了要在切入點(diǎn)上執(zhí)行的代碼片,而方面則是這些基礎(chǔ)元素的組合。
另外,AspectJ允許以多種方式用方面和類建立新的方面,你可以引入新的數(shù)據(jù)成員和方法,或是聲明一個(gè)新的類來(lái)繼承和實(shí)現(xiàn)另外的類或接口。
AspectJ的織入器——AspectJ的編譯器——負(fù)責(zé)把不同的方面組合在一起,由于由AspectJ編譯器建立的最終系統(tǒng)是純Java字節(jié)碼,它可以運(yùn)行在任何符合Java標(biāo)準(zhǔn)的虛擬機(jī)上。而且,AspectJ還提供了一些工具如調(diào)試器和Java IDE集成等,我將會(huì)在本系列的第二、三部分詳細(xì)講解這些。
下面是我在上面用自然語(yǔ)言描述的日志方面的織入規(guī)則的AspectJ實(shí)現(xiàn),由于我將會(huì)在第二部分詳細(xì)介紹AspectJ,所以如果你不能透徹的看懂它的話也不必?fù)?dān)心。關(guān)鍵是你應(yīng)該注意到信用卡處理過(guò)程本身一點(diǎn)都不知道日志的事。
代碼: |
public aspect LogCreditCardProcessorOperations { ? ? Logger logger = new StdoutLogger(); ? ? pointcut publicOperation(): ? ? ? ? execution(public * CreditCardProcessor.*(..)); ? ? pointcut publicOperationCardAmountArgs(CreditCard card, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Money amount): ? ? ? ? publicOperation() && args(card, amount); ? ? before(CreditCard card, Money amount): ? ? ? ? publicOperationCardAmountArgs(card, amount) { ? ? ? ? logOperation("Starting", ? ? ? ? ? ? ?thisjoin point.getSignature().toString(), card, amount); ? ? } ? ? after(CreditCard card, Money amount) returning: ? ? ? ? publicOperationCardAmountArgs(card, amount) { ? ? ? ? logOperation("Completing", ? ? ? ? ? ? thisjoin point.getSignature().toString(), card, amount); ? ? } ? ? after (CreditCard card, Money amount) throwing (Exception e): ? ? ? ? publicOperationCardAmountArgs(card, amount) { ? ? ? ? logOperation("Exception " + e, ? ? ? ? ? ? thisjoin point.getSignature().toString(), card, amount); ? ? } ? ? private void logOperation(String status, String operation, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CreditCard card, Money amount) { ? ? ? ? logger.log(status + " " + operation + ? ? ? ? ? ? ? ? ? ?" Card: " + card + " Amount: " + amount); ? ? } } |
我需要AOP嗎?
AOP僅僅是解決設(shè)計(jì)上的缺點(diǎn)嗎?在AOP里,每個(gè)關(guān)注點(diǎn)的實(shí)現(xiàn)的并不知道是否有其它關(guān)注點(diǎn)關(guān)注它,這是AOP和OOP的主要區(qū)別,在AOP里,組合的流向是從橫切關(guān)注點(diǎn)到主關(guān)注點(diǎn),而OOP則相反,但是,OOP可以和AOP很好的共存。比如,你可以使用一個(gè)混入類來(lái)做組合,既可以用AOP實(shí)現(xiàn),也可以用OOP實(shí)現(xiàn),這取決你對(duì)AOP的接受程度。在這兩種情況下,實(shí)現(xiàn)橫切關(guān)注點(diǎn)的混入類實(shí)現(xiàn)都無(wú)需知道它自己是被用在類中還是被用在方面中。舉個(gè)例子來(lái)說(shuō),你可以把一個(gè)日志寫(xiě)入器接口用作某些類的混入類或是用作一個(gè)日志方面。因而,從OOP到AOP是漸進(jìn)的。
了解AOP
在這篇文章里,你看到了橫切關(guān)系帶來(lái)的問(wèn)題,這些問(wèn)題的當(dāng)前解決方法,以及這些方法的缺點(diǎn)。你也看到了AOP是怎樣克服這些缺點(diǎn)的。AOP的編程方式試圖模塊化橫切關(guān)注點(diǎn)的實(shí)現(xiàn),提供了一個(gè)更好更快的軟件開(kāi)發(fā)方式。
如果你的系統(tǒng)中涉及到多個(gè)橫切關(guān)注點(diǎn),你可以考慮進(jìn)一步了解AOP,它的實(shí)現(xiàn),它的好處。AOP很可能會(huì)是編程方式的下一個(gè)里程碑。請(qǐng)繼續(xù)關(guān)注本系列的第二、第三部分。
posted on 2006-10-25 10:06 捕風(fēng) 閱讀(225) 評(píng)論(0) 編輯 收藏 所屬分類: java高級(jí)