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

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

圖 2. 關(guān)注點分解: 三棱鏡法則
開發(fā)人員建立一個系統(tǒng)以滿足多個需求,我們可以大致的把這些需求分類為核心模塊級需求和系統(tǒng)級需求。很多系統(tǒng)級需求一般來說是相互獨(dú)立的,但它們一般都會橫切許多核心模塊。舉個例子來說,一個典型的企業(yè)應(yīng)用包含許多橫切關(guān)注點,如驗證,日志,資源池,系統(tǒng)管理,性能及存儲管理等,每一個關(guān)注點都牽涉到幾個子系統(tǒng),如存儲管理關(guān)注點會影響到所有的有狀態(tài)業(yè)務(wù)對象。
讓我們來看一個簡單,但是具體的例子,考慮一個封裝了業(yè)務(wù)邏輯的類的實現(xiàn)框架:
代碼: |
public class SomeBusinessClass extends OtherBusinessClass { ? ? // 核心數(shù)據(jù)成員
? ? // 其它數(shù)據(jù)成員:日志流,保證數(shù)據(jù)完整性的標(biāo)志位等
? ? // 重載基類的方法
? ? public void performSomeOperation(OperationInformation info) { ? ? ? ? // 安全性驗證
? ? ? ? // 檢查傳入數(shù)據(jù)是否滿足協(xié)議
? ? ? ? // 鎖定對象以保證當(dāng)其他線程訪問時的數(shù)據(jù)完整性
? ? ? ? // 檢查緩存中是否為最新信息
? ? ? ? // 紀(jì)錄操作開始執(zhí)行時間
? ? ? ? // 執(zhí)行核心操作
? ? ? ? // 紀(jì)錄操作完成時間
? ? ? ? // 給對象解鎖 ? ? }
? ? // 一些類似操作
? ? public void save(PersitanceStorage ps) { ? ? }
? ? public void load(PersitanceStorage ps) { ? ? } } |
在上面的代碼中,我們注意到三個問題,首先,其它數(shù)據(jù)成員不是這個類的核心關(guān)注點,第二,performSomeOperation()的實現(xiàn)做了許多核心操作之外的事,它要處理日志,驗證,線程安全,協(xié)議驗證和緩存管理等一些外圍操作,而且這些外圍操作同樣也會應(yīng)用于其他類,第三,save()和load()執(zhí)行的持久化操作是否構(gòu)成這個類的核心清楚的。
橫切關(guān)注點的問題
雖然橫切關(guān)注點會跨越多個模塊,但當(dāng)前的技術(shù)傾向于使用一維的方法學(xué)來處理這種需求,把對應(yīng)需求的實現(xiàn)強(qiáng)行限制在一維的空間里。這個一維空間就是核心模塊級實現(xiàn),其他需求的實現(xiàn)被嵌入在這個占統(tǒng)治地位的空間,換句話說,需求空間是一個n維空間,而實現(xiàn)空間是一維空間,這種不匹配導(dǎo)致了糟糕的需求到實現(xiàn)的映射
表現(xiàn)
用當(dāng)前方法學(xué)實現(xiàn)橫切關(guān)注點是不好的,它會帶來一些問題,我們可以大致把這些問題分為兩類:
- 代碼混亂:軟件系統(tǒng)中的模塊可能要同時兼顧幾個方面的需要。舉例來說,開發(fā)者經(jīng)常要同時考慮業(yè)務(wù)邏輯,性能,同步,日志和安全等問題,兼顧各方面的需要導(dǎo)致相應(yīng)關(guān)注點的實現(xiàn)元素同時出現(xiàn),引起代碼混亂。
- 代碼分散:由于橫切關(guān)注點,本來就涉及到多個模塊,相關(guān)實現(xiàn)也就得遍布在這些模塊里,如在一個使用了數(shù)據(jù)庫的系統(tǒng)里,性能問題就會影響所有訪問數(shù)據(jù)庫的模塊。這導(dǎo)致代碼分散在各處
結(jié)果
混亂和分散的代碼會在多個方面影響系統(tǒng)的設(shè)計和開發(fā):
- 可讀性差:同時實現(xiàn)幾個關(guān)注點模糊了不同關(guān)注點的實現(xiàn),使得關(guān)注點與其實現(xiàn)之間的對應(yīng)關(guān)系不明顯。
- 低產(chǎn)出:同時實現(xiàn)幾個關(guān)注點把開發(fā)人員的注意力從主要的轉(zhuǎn)移到外圍關(guān)注點,導(dǎo)致產(chǎn)能降低。
- 低代碼重用率:由于這種情況下,一個模塊實現(xiàn)多個關(guān)注點,其他需要類似功能的系統(tǒng)不能馬上使用該模塊,進(jìn)一步降低了產(chǎn)能。
- 代碼質(zhì)量差:混亂的代碼掩蓋了代碼中隱藏的問題。而且,由于同時要處理多個關(guān)注點,應(yīng)該特別注意的關(guān)注點得不到應(yīng)有的關(guān)注
- 難以擴(kuò)展:狹窄的視角和有限的資源總是產(chǎn)生僅注意當(dāng)前關(guān)注點的設(shè)計。新的需求導(dǎo)致從新實現(xiàn)。由于實現(xiàn)不是模塊化的,就是說實現(xiàn)牽涉到多個模塊,為了新需求修改子系統(tǒng)可能會帶來數(shù)據(jù)的不一致,而且還需相當(dāng)規(guī)模測試來保證這些修改不會帶來bug。
當(dāng)前解決方法
由于多數(shù)系統(tǒng)中都包含橫切關(guān)注點,自然的已經(jīng)形成了一些技術(shù)來模塊化橫切關(guān)注點的實現(xiàn),這些技術(shù)包括:混入類,設(shè)計模式和面向特定問題域的解決方式
使用混入類,你可以推遲關(guān)注點的最終實現(xiàn)。基本類包含一個混入類的實例,允許系統(tǒng)的其他部分設(shè)置這個實例,舉個例子來說,實現(xiàn)業(yè)務(wù)邏輯的類包含一個混入的logger,系統(tǒng)的其他部分可以設(shè)置這個logger已得到合適的日志類型,比如logger可能被設(shè)置為使用文件系統(tǒng)或是消息中間件.在這種方式下,雖然日志的具體實現(xiàn)被推遲啦,基本類還是得包含在所有的寫日志的點調(diào)用日志操作和控制日志信息的代碼。
行為型設(shè)計模式,如Visitor和Template模式,也允許你推遲具體實現(xiàn)。但是也就像混入類一樣,操作的控制——調(diào)用visitor或template的邏輯——仍然留給了基本類
面向特定問題域的解決方式,如框架和應(yīng)用服務(wù)器,允許開發(fā)者用更模塊化的方式處理某些橫切關(guān)注點。比如EJB(Enterprise JavaBean,企業(yè)級javabean)架構(gòu),可以處理安全,系統(tǒng)管理,性能和容器管理的持久化(container-managed persistence)等橫切關(guān)注點。Bean的開發(fā)者僅需關(guān)心業(yè)務(wù)邏輯,而部署者僅需關(guān)心部署問題,如bean與數(shù)據(jù)庫的映射。但是大多數(shù)情況下,開發(fā)者還是要了解存儲結(jié)構(gòu)。這種方式下,你用基于XML的映射關(guān)系描述器來實現(xiàn)于數(shù)據(jù)持久化相關(guān)的橫切關(guān)注點。
面向特定問題域的解決方式提供了解決特定問題的專門機(jī)制,它的缺點是對于每一種這樣的解決方式開發(fā)人員都必須重新學(xué)習(xí),另外,由于這種方式是特定問題域相關(guān)的,屬于特定問題域之外的橫切關(guān)注點需要特殊的對待
設(shè)計師的兩難局面
好的系統(tǒng)設(shè)計師不僅會考慮當(dāng)前需求,還會考慮到可能會有的需求以避免到處打補(bǔ)丁。這樣就存在一個問題,預(yù)知將來是很困難的,如果你漏過了將來可能會有的橫切關(guān)注點的需求,你將會需要修改或甚至是重新實現(xiàn)系統(tǒng)的許多部分;從另一個角度來說,太過于關(guān)注不一定需要的需求會導(dǎo)致過分設(shè)計(overdesigned)的,難以理解的,臃腫的系統(tǒng)。所以系統(tǒng)設(shè)計師處在這么一個兩難局面中:怎么設(shè)計算是過分設(shè)計?應(yīng)該寧可設(shè)計不足還是寧可過分設(shè)計?
舉個例子來說,設(shè)計師是否應(yīng)該在系統(tǒng)中包含現(xiàn)在并不需要的日志機(jī)制?如果是的話,哪里是應(yīng)該寫日志的點?日志應(yīng)該記錄那些信息?相似的例子還有關(guān)于性能的優(yōu)化問題,我們很少能預(yù)先知道瓶頸的所在。常用的方法是建立系統(tǒng),profile它,然后翻新系統(tǒng)以提高性能,這種方式可能會依照profiling修改系統(tǒng)的很多部分,此外,隨著時間的流逝,由于使用方式的變化,可能還會產(chǎn)生新的瓶頸,類庫設(shè)計師的任務(wù)更困難,因為他很難設(shè)想出所有對類庫的使用方式。
總而言之,設(shè)計師很難顧及到系統(tǒng)可能需要處理的所有關(guān)注點。即使是在已經(jīng)知道了需求的前提下,某些建立系統(tǒng)時需要的細(xì)節(jié)也可能不能全部得到。整體設(shè)計就面臨著設(shè)計不足/過分設(shè)計的兩難局面。
AOP基礎(chǔ)
到目前為止的討論說明模塊化橫切關(guān)注點是有好處的。研究人員已經(jīng)嘗試了多種方法來實現(xiàn)這個任務(wù),這些方法有一個共同的主題:分離關(guān)注點。AOP是這些方法中的一種,它的目的是清晰的分離關(guān)注點來解決以上提到的問題。
AOP,從其本質(zhì)上講,使你可以用一種松散耦合的方式來實現(xiàn)獨(dú)立的關(guān)注點,然后,組合這些實現(xiàn)來建立最終系統(tǒng)。用它所建立的系統(tǒng)是使用松散耦合的,模塊化實現(xiàn)的橫切關(guān)注點來搭建的。與之對照,用OOP建立的系統(tǒng)則是用松散耦合的模塊化實現(xiàn)的一般關(guān)注點來實現(xiàn)的。在AOP終,這些模塊化單元叫方面(aspect),而在OOP中,這些一般關(guān)注點的實現(xiàn)單元叫做類。
AOP包括三個清晰的開發(fā)步驟:
- 方面分解:分解需求提取出橫切關(guān)注點和一般關(guān)注點。在這一步里,你把核心模塊級關(guān)注點和系統(tǒng)級的橫切關(guān)注點分離開來。就前面所提到的信用卡例子來說,你可以分解出三個關(guān)注點:核心的信用卡處理,日志和驗證。
- 關(guān)注點實現(xiàn):各自獨(dú)立的實現(xiàn)這些關(guān)注點,還用上面信用卡的例子,你要實現(xiàn)信用卡處理單元,日志單元和驗證單元。
- 方面的重新組合:在這一步里,方面集成器通過創(chuàng)建一個模塊單元——方面來指定重組的規(guī)則。重組過程——也叫織入或結(jié)合——則使用這些信息來構(gòu)建最終系統(tǒng),還拿信用卡的那個例子,你可以指定(用某種AOP的實現(xiàn)所提供的語言)每個操作的開始和結(jié)束需要紀(jì)錄,并且每個操作在涉及到業(yè)務(wù)邏輯之前必須通過驗證。

圖 3. AOP 開發(fā)的步驟
AOP與OOP的不同關(guān)鍵在于它處理橫切關(guān)注點的方式,在AOP中,每個關(guān)注點的實現(xiàn)都不知道其它關(guān)注點是否會‘關(guān)注’它,如信用卡處理模塊并不知道其它的關(guān)注點實現(xiàn)正在為它做日志和驗證操作。它展示了一個從OOP轉(zhuǎn)化來的強(qiáng)大的開發(fā)范型。
注意:一個AOP實現(xiàn)可以借助其它編程范型作為它的基礎(chǔ),從而原封不動的保留其基礎(chǔ)范型的優(yōu)點。例如,AOP可以選擇OOP作為它的基礎(chǔ)范型,從而把OOP善于處理一般關(guān)注點的好處直接帶過來。用這樣一種實現(xiàn),獨(dú)立的一般關(guān)注點可以使用OOP技術(shù)。這就像過程型語言是許多OOP語言的基礎(chǔ)一樣。
織入舉例
織入器——一個處理器——組裝一個個關(guān)注點(這個過程叫做織入)。就是說,它依照提供給它的規(guī)則把不同的執(zhí)行邏輯段混編起來。
為了說明代碼織入,讓我們回到信用卡處理的例子,為了簡單起見,我們只考慮兩個操作:存入和取出,并且我們假設(shè)已經(jīng)有了一個合適的logger.
來看一下下面的信用卡模塊:
- 紀(jì)錄每個公共操作的開始
- 紀(jì)錄每個公共操作的結(jié)束
- 紀(jì)錄所有公共方法拋出的異常
織入器就會使用這些織入規(guī)則和關(guān)注點實現(xiàn)來產(chǎn)生與如下代碼有相同效果的代碼:
代碼: |
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ī)則,這里用自然語言來表達(dá)(本文的后面會提供這些織入規(guī)則的程序版本):
[list=a]
代碼: |
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語言剖析
就像其他編程范型的實現(xiàn)一樣,AOP的實現(xiàn)有兩部分組成:語言規(guī)范和實現(xiàn)。語言規(guī)范描述了語言的基礎(chǔ)單元和語法。語言實現(xiàn)則按照語言規(guī)范來驗證代碼的正確性并把代碼轉(zhuǎn)成目標(biāo)機(jī)器的可執(zhí)行形式。這一節(jié),我來解釋一下AOP組成部分。
AOP語言規(guī)范
從抽象的角度看來,一種AOP語言要說明下面兩個方面:
- 關(guān)注點的實現(xiàn):把每個需求映射為代碼,然后,編譯器把它翻譯成可執(zhí)行代碼,由于關(guān)注點的實現(xiàn)以指定過程的形式出現(xiàn),你可以使用傳統(tǒng)語言如C,C++,Java等。
- 織入規(guī)則規(guī)范:怎樣把獨(dú)立實現(xiàn)的關(guān)注點組合起來形成最終系統(tǒng)呢?為了這個目的,需要建立一種語言來指定組合不同的實現(xiàn)單元以形成最終系統(tǒng)的規(guī)則,這種指定織入規(guī)則的語言可以是實現(xiàn)語言的擴(kuò)展,也可以是一種完全不同的語言。
AOP語言的實現(xiàn)
AOP的編譯器執(zhí)行兩步操作:
- 組裝關(guān)注點。
- 把組裝結(jié)果轉(zhuǎn)成可執(zhí)行代碼
AOP實現(xiàn)可以用多種方式實現(xiàn)織入,包括源碼到源碼的轉(zhuǎn)換。它預(yù)處理每個方面的源碼產(chǎn)生織入過的源碼,然后把織入過的源碼交給基礎(chǔ)語言的編譯器產(chǎn)生最終可執(zhí)行代碼。比如,使用這種方式,一個基于Java的AOP實現(xiàn)可以先把不同的方面轉(zhuǎn)化成Java源代碼,然后讓Java編譯器把它轉(zhuǎn)化成字節(jié)碼。也可以直接在字節(jié)碼級別執(zhí)行織入;畢竟,字節(jié)碼本身也是一種源碼。此外,下面的執(zhí)行系統(tǒng)——Java虛擬機(jī)——也可以是方面認(rèn)知的,基于Java的AOP實現(xiàn)如果使用這種方式的話,虛擬機(jī)可以先裝入織入規(guī)則,然后對后來裝入的類都應(yīng)用這種規(guī)則,也就是說,它可以執(zhí)行just-in-time的方面織入。
AOP的好處
AOP可幫助我們解決上面提到的代碼混亂和代碼分散所帶來的問題,它還有一些別的好處:
- 塊化橫切關(guān)注點:AOP用最小的耦合處理每個關(guān)注點,使得即使是橫切關(guān)注點也是模塊化的。這樣的實現(xiàn)產(chǎn)生的系統(tǒng),其代碼的冗余小。模塊化的實現(xiàn)還使得系統(tǒng)容易理解和維護(hù)
- 系統(tǒng)容易擴(kuò)展:由于方面模塊根本不知道橫切關(guān)注點,所以很容易通過建立新的方面加入新的功能,另外,當(dāng)你往系統(tǒng)中加入新的模塊時,已有的方面自動的橫切進(jìn)來,使系統(tǒng)的易于擴(kuò)展
- 設(shè)計決定的遲綁定:還記得設(shè)計師的兩難局面嗎?使用AOP,設(shè)計師可以推遲為將來的需求作決定,因為它可以把這種需求作為獨(dú)立的方面很容易的實現(xiàn)。
- 更好的代碼重用性:由于AOP把每個方面實現(xiàn)為獨(dú)立的模塊,模塊之間是松散耦合的,舉例來說,你可以用另外一個獨(dú)立的日志寫入器方面(替換當(dāng)前的)把日志寫入數(shù)據(jù)庫,以滿足不同的日志寫入要求。
總的來說,松散耦合的實現(xiàn)意味著更好的代碼重用性, AOP在使系統(tǒng)實現(xiàn)松散耦合這一點上比OOP做得更好。
AspectJ:一個Java的AOP實現(xiàn)
AspectJ是一個可免費(fèi)獲得的由施樂公司帕洛阿爾托研究中心(Xerox PARC)開發(fā)Java的AOP實現(xiàn),它是一個多功能的面向方面的Java擴(kuò)展。它使用Java作為單個關(guān)注點的實現(xiàn)語言,并擴(kuò)展Java以指定織入規(guī)則。這些規(guī)則是用切入點(pointcuts)、聯(lián)結(jié)點(join points),通知(advice)和方面(aspect)來說明的。聯(lián)結(jié)點是定義在程序執(zhí)行過程之間的點,切入點由用來指定聯(lián)結(jié)點的語言構(gòu)造,通知定義了要在切入點上執(zhí)行的代碼片,而方面則是這些基礎(chǔ)元素的組合。
另外,AspectJ允許以多種方式用方面和類建立新的方面,你可以引入新的數(shù)據(jù)成員和方法,或是聲明一個新的類來繼承和實現(xiàn)另外的類或接口。
AspectJ的織入器——AspectJ的編譯器——負(fù)責(zé)把不同的方面組合在一起,由于由AspectJ編譯器建立的最終系統(tǒng)是純Java字節(jié)碼,它可以運(yùn)行在任何符合Java標(biāo)準(zhǔn)的虛擬機(jī)上。而且,AspectJ還提供了一些工具如調(diào)試器和Java IDE集成等,我將會在本系列的第二、三部分詳細(xì)講解這些。
下面是我在上面用自然語言描述的日志方面的織入規(guī)則的AspectJ實現(xiàn),由于我將會在第二部分詳細(xì)介紹AspectJ,所以如果你不能透徹的看懂它的話也不必?fù)?dān)心。關(guān)鍵是你應(yīng)該注意到信用卡處理過程本身一點都不知道日志的事。
代碼: |
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è)計上的缺點嗎?在AOP里,每個關(guān)注點的實現(xiàn)的并不知道是否有其它關(guān)注點關(guān)注它,這是AOP和OOP的主要區(qū)別,在AOP里,組合的流向是從橫切關(guān)注點到主關(guān)注點,而OOP則相反,但是,OOP可以和AOP很好的共存。比如,你可以使用一個混入類來做組合,既可以用AOP實現(xiàn),也可以用OOP實現(xiàn),這取決你對AOP的接受程度。在這兩種情況下,實現(xiàn)橫切關(guān)注點的混入類實現(xiàn)都無需知道它自己是被用在類中還是被用在方面中。舉個例子來說,你可以把一個日志寫入器接口用作某些類的混入類或是用作一個日志方面。因而,從OOP到AOP是漸進(jìn)的。
了解AOP
在這篇文章里,你看到了橫切關(guān)系帶來的問題,這些問題的當(dāng)前解決方法,以及這些方法的缺點。你也看到了AOP是怎樣克服這些缺點的。AOP的編程方式試圖模塊化橫切關(guān)注點的實現(xiàn),提供了一個更好更快的軟件開發(fā)方式。
如果你的系統(tǒng)中涉及到多個橫切關(guān)注點,你可以考慮進(jìn)一步了解AOP,它的實現(xiàn),它的好處。AOP很可能會是編程方式的下一個里程碑。請繼續(xù)關(guān)注本系列的第二、第三部分。?