通過面向特徵的編程減少編碼時間和重複。目前編程中最熱門的新概念是面向特徵的編程(ASpECt-oriented Programming,AOP)。AOP曾經主要用於學術和研發機構,如今開始進入主流開發領域。與OOP在面向過程的編程方法基礎上的改進一樣,AOP是在物件導向編程(OOP)方法的基礎上進行改進而來的一種創新的軟體開發方法。OOP引入了封裝、繼承和多態性等概念來建立一種對象階層,用以類比公用行為的一個集合。然而,OOP在處理範圍擴充到一些無關對象的公用行為方面達不到要求。也就是說,OOP允許你定義從上到下的關係,但並不適合定義從左至右的關係。例如,看一下日誌功能。日誌代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關係。對於其他類型的代碼,如安全性、異常處理和透明的持久性也是如此。這種散布在各處的無關的代碼被稱為橫切(CROSs-cutting)代碼,這也是AOP編碼方法產生的原因。
AOP提供一種提取橫切代碼的方法,這種橫切代碼橫跨各個對象層次,但與它所跨越的對象代碼在功能上沒有相關性。AOP不是在類中嵌入橫切代碼,而是允許你將橫切代碼提取到一個單獨的模組中,然後在需要的時候動態地應用該代碼,這個單獨的模組叫做一個"特徵代碼"("aspect",也譯作"標記")。通過在你的物件模型中需要應用橫切代碼的地方定義特定的位置--切入點(pointcut)--來實現動態應用橫切代碼。在運行或編譯時間,根據你的AOP架構,橫切代碼被插入指定的切入點。本質上說,AOP允許你在對象中引入新功能,而對象無需瞭解所引入的功能。這是一個非常有用的概念。
為了進一步理解AOP是如何工作的,請看一個典型的AOP日誌例子。代碼清單1顯示了兩個包含日誌代碼的簡單對象:objectA和ObjectB。
通過標準的物件導向的編程,每次你需要時,都要在相應的對象中編寫日誌代碼。在代碼清單1的例子中,使用SySTem.out.println()調用來記日誌很煩瑣。代替System.out.println()調用的另一種方法是使用log4j這樣的日誌架構,但這會帶來額外的開銷並給使用它的類帶來不必要的雜亂東西。無論是使用System.out.println()調用還是日誌架構來實現日誌功能,日誌代碼都與它被嵌入到的類在功能上沒有相關性。
通過AOP,你可以動態地將日誌代碼插入到需要日誌功能的類中。這樣,對象可以專註於其核心職責。只專註於其核心職責的Java對象往往被稱為POJOs(PlAIn Old Java Objects):普通老式Java對象。你可以使用AOP給POJOs增加日誌和一些其他類型的公用功能,而無需嵌入不必要的無關功能。
本文將介紹和解釋AOP術語,解釋不同AOP架構之間的差別,然後逐步說明一個將AOP用於緩衝的樣本。
AOP術語
AOP 引入了幾個新的術語來描述其基本概念。必須切實理解這些術語才可能理解AOP。下面列出了AOP引入的術語及其描述:
1. 建議--是應用到或橫切你現有物件模型的代碼。建議代碼就是修改已有對象的行為或屬性的代碼。建議通常也被稱為引入件(introductions)或混入件(mix-ins)。
2. 切入點-定義在你的模型中要應用建議的位置點。例如,切入點定義在一個類的什麼位置應引入代碼或哪個方法要在執行前被攔截。切入點也被通稱為連接點(joinpoints)。
3. 特徵-它將建議和切入點封裝到功能單元中,其封裝方式與OOP使用類將欄位和方法封裝到內聚單元(cohesive unit)的方式基本相同。例如,你可能有一個日誌特徵,它包含了將日誌代碼應用於對象的所有設定(setter)和擷取(getter)方法的建議和切入點。
選擇一個架構
在開始使用AOP之前,必須選擇一個所用的AOP架構。Java和任何其他主流的物件導向的程式設計語言都沒有對AOP提供內建支援。不過,有一些支援Java的可用的AOP架構,其中大多數都有著相同的核心功能,但在AOP串連到物件模型的方式上有差別。
一些AOP架構使用位元組碼操作來串連到物件模型,其他一些則使用基於代理的系統進行串連。使用位元組碼方法的架構在原始碼編譯為位元組碼之前修改原始碼或在編譯後修改位元組碼。這兩種方式都可以有效地得到相同的結果:經過修改的位元組碼將AOP嵌入到了原始代碼。基於代理的架構使用一個代理系統,AOP架構籍此截取所有對具有某特徵代碼對象的方法調用,然後代理執行對這些架構想要的對象的方法調用。這種代理功能是透明的,而且可以保持位元組碼總是不變。
各個AOP架構之間的另一個核心區別是特徵代碼的定義和應用的方式。有些架構中,通過代碼來定義和應用特徵,而其他一些架構則要求通過XML配置檔案來定義和應用特徵代碼。更好的是,一些架構同時支援用代碼和XML設定檔定義和應用特徵代碼。
因為大多數AOP架構的功能有重疊,究竟使用哪個架構往往取決於某個特定架構的獨特功能。本文中的AOP樣本應用程式是用一個新的AOP架構dynaop開發的。但是,這個樣本應用的基本原理是通用的,可應用於所有的AOP架構。dynaop架構支援用代碼定義和應用特徵代碼,它還提供了一個通過BeanShell指令碼架構定義和應用特徵代碼的獨特解決方案。 BeanShell指令碼可以使你輕鬆地編寫Java對象指令碼,比如應用程式範例所示。
通過dynaop使用AOP
一個簡單的緩衝特徵對方法的結果進行緩衝,它說明了AOP的一些基本概念。第一次對方法的調用具有實用的緩衝特徵代碼,它調用該方法並緩衝其執行的結果。對這個方法的後續調用將從緩衝中返回方法的調用結果。這個基本的緩衝特徵代碼對於已耗用時間長的方法非常有用,諸如處理大量資料的方法或對某個資料來源發出多個查詢的方法等。
如代碼清單1中的日誌例子,在每個需要的類中嵌入緩衝功能會導致大量的重複代碼。而且,緩衝對於大多數對象並不是一個核心功能,而很適合用AOP來實現統一和重複使用。
這個緩衝應用程式套件組合括四個檔案:
CachingInterceptor.java-包含對方法調用結果進行緩衝的AOP代碼。這些代碼被稱為建議。
User.java-一個基本類,其中包含對"name"欄位進行設定和擷取"getter"和"setter"的方法。 CachingInterceptor AOP代碼將被用於這個類。
CacheTest.java-一個說明CachingInterceptor AOP代碼使用方式的應用程式範例。
dynaop.bsh-規定在哪裡應用CachingInterceptor advice的BeanShell指令碼。這些規定被稱為切入點(pointcuts)。
以下部分將詳細解釋每個檔案。
CachingInterceptor.java檔案。CachingInterceptor類以一種標準、通用的方式將可以應用到任何類的緩衝代碼進行封裝。這個類將方法初次被調用後的結果緩衝起來。然後CachingInterceptor類攔截對其結果已被緩衝的方法的後續調用,並直接從緩衝中返回這些結果,從而為已耗用時間很長的方法提供快速響應。代碼清單2顯示了CachingInterceptor類。
CachingInterceptor類包含intercept()、calculateCacheCode()和getFullMethodName()各個方法。intercept()方法實現dynaop.Interceptor介面並包含這個特徵的核心建議代碼。該方法在具有特徵代碼的方法被調用之前被調用,然後它負責調用這個具有特徵代碼的方法。重要的是,intercept()方法是一個代理,用於插入在該方法被代理前執行的功能。另外,intercept()方法控制代理方法是否真正被調用。
CachingInterceptor類的intercept()方法調用calculateCacheCode()方法為將被調用的方法計算一個緩衝索引值。然後使用這個索引值為被調用的方法(在本例中是User類中的getName()方法,在後面部分有定義)尋找緩衝,查看是否其結果已被緩衝。如果結果已經被緩衝,則返回被緩衝的結果,不再調用User.getName()方法。如果結果沒有被緩衝,則調用User.getName()方法,並將其結果緩衝起來供後續調用使用。這是一個基本的緩衝實現,不考慮緩衝中的時效資料項目和緩衝是否超長。
User.java 檔案。User類是一個簡單的類,只有一個欄位以及針對該欄位的擷取和設定方法。 CachingInterceptor特徵代碼被用於這個類的getName()方法。代碼清單3 顯示了User類。
請注意,這個類中的每個方法都有一個System.out.println()調用,指明該方法什麼時候被調用。這些調用用來說明樣本應用運行時究竟會產生什麼結果。 下一步
瞭解更多AOP資訊
aosd.net
eclipse.org/AspectJ
BeanShell
下載
dynaop
本應用程式
閱讀更多James Holmes的文章
Java藝術
Struts: 完整的參考手冊
CacheTest.java 檔案。CacheTest類包含說明CachingInterceptor特徵代碼如何使用的應用程式範例的代碼。這個應用程式只是執行個體化了一個User對象,並多次調用它的getName()方法,顯示出連續調用緩衝中的方法結果以及對該方法的直接存取。代碼清單4顯示了CacheTest類。
CacheTest包含一個main()方法,所以它可以作為一個獨立的應用程式來運行。
dynaop架構使用一個基於運行時的編織機制將AOP代碼插入對象中,這樣來直接執行個體化User對象,使用這個新的操作符,而不是返回一個具有特徵代碼的對象。為了在應用程式範例中使用CachingInterceptor特徵代碼,CacheTest類通過dynaop的ProxyFactory類來執行個體化User對象。一旦你通過ProxyFactory.getInstance() .extend()執行個體化了這個對象,你就可以像使用其他任何對象一樣正常地使用User對象。從這點來說AOP代碼的執行是透明的。
dynaop.bsh檔案。dynaop.bsh檔案是一個BeanShell指令碼,用於指定應用CachingInterceptor建議的切入點。以下是dynaop.bsh檔案的內容:
// Apply interceptor to all
// getter methods.
interceptor
(
User.Class,
GET_METHODS,
new CachingInterceptor()
);
這個簡單指令碼指定了CachingInterceptor建議將應用到User類的所有擷取方法。GET_METHODS是dynaop用來方便地指定一組切點的幾個常量之一。如果必要,你也可以明確地為一些切點指定各個方法。
編譯和運行這個應用程式
最後,編譯和運行這個應用程式。假設你將這個應用程式的四個檔案放在了你安裝的dynaop的目錄下,例如c:/java/dynaop,下面的命令列將編譯這個應用程式:
javac -classpath ./bsh-2.0b1.jar;
./cglib-asm-1.0.jar;
./dynaop-1.0-beta.jar;
./jakarta-oro-2.0.7.jar
*.java
編譯完後,執行以下命令運行它:
java -classpath ./;
./bsh-2.0b1.jar;
./cglib-asm-1.0.jar;
./dynaop-1.0-beta.jar;
./jakarta-oro-2.0.7.jar
CacheTest
圖1顯示了這個應用程式範例的運行結果。這個應用程式先調用User類的setName()方法,然後調用getName()方法。這兩個調用都在User類中被啟用,對getName()的後續調用在緩衝中尋找到結果,而不再在User類中被啟用。
無提示--不好意思!
-----
圖1: 命令列提示
結論
AOP提供了一個建立軟體的功能豐富的新平台,它去除了類的一些不必要職責,並極大地促進了代碼的重用。 AOP的日常實際使用包括提取日誌代碼、提取安全性和緩衝代碼等,但AOP的應用遠不止於此。隨著AOP的日益成熟和發展,它還將會有更多的應用。