1.方面庫
大家都知道,各種AOP工具的核心就是切入點(pointcut)和通知(advice) 的聲明。切入點描述了主程序執行與方面執行相遇的地方,也就是被橫切的位置 ;通知則描述了在程序執行過程中遇到匹配的切入點時應當采取什麼行動。假設 已經開發了一個方面,並且感覺它適用於其他項目,那麼可以泛化這個方面,並 把它隔離到自己獨立的項目中,形成一個庫,即方面庫(Aspect Library)。方 面庫提供了某個功能的內部執行邏輯和基礎設施,通過切入點的實例化將方面庫 與某個特定項目連接起來。例如提供應用程序性能監視的方面庫,實現了所有性 能監視相關的方法和通知,某應用程序使用該庫的時候,只需要把庫的切入點定 義為應用特定的連接點即可,而無需關心性能監視功能的具體實現。這就是方面 庫的基本概念。
方面庫是AOP工具具有擴展性的體現,目前常用的AOP工具,如JBoss AOP、 Spring AOP、AspectJ等,都有對方面庫的支持,但由於它們實現AOP的方法不同 ,方面庫的定義和使用方法也不相同。在JBoss AOP和Spring AOP中,通知的實現 都是通過普通Java語法定義,切入點到通知的綁定是通過顯式的XML文檔或者注釋 實現的。所以將方面庫應用於應用程序就可以很容易地通過在 XML 中或通過注釋 定義新的通知綁定而實現。並且JBoss和Spring系統本身已經提供了很好的方面庫 供用戶使用。
AspectJ是Java 語言語法和語義的擴展,它提供了自己的一套處理方面的關鍵 字,這些都決定了用AspectJ構造方面庫的方法具有一定的特殊性。所以AspectJ 構造的方面庫一直不象JBoss AOP和Spring AOP那樣普遍,AspectJ也並沒有提供 任何方面庫。令人欣慰的是, AspectJ從AspectJ5版本開始提供對注釋的支持, 它的編輯工具AJDT中逐漸加入了對方面庫的支持功能,從而使得使用AspectJ構造 方面庫變得越來越容易。本文就來介紹如何用AspectJ構造方面庫。
2.AspectJ對方面庫的技術支持
方面庫的實現在於切入點的實例化方式。AspectJ對切入點定義方法的支持導 致了兩種完全不同的方面庫實現方法--使用抽象方面(abstract aspect)的方法 和使用注釋的方法。本章將詳細介紹這兩種方式。
AspectJ是Java語言的擴展。方面在AspectJ語言中用aspect關鍵字標示,它類 似於Java類,可以定義成抽象的,也存在繼承關系。切入點在AspectJ語言中用關 鍵字pointcut標示,它有一套完整的語法來描述切入點,也可以定義成抽象的, 即沒有實際定義的切入點。抽象 pointcut只能定義在抽象方面中。如清單 1所示 ,抽象方面A裡面定義了一個抽象pointcut名叫publicCall。抽象方面如同抽象類 一樣,可以被繼承,繼承抽象方面的方面必須重載抽象 pointcut,即賦予抽象 pointcut實際的定義。如清單 2所示,方面B繼承了抽象方面A,重載了名叫 publicCall的pointcut,給它一個具體的定義。
清單 1 抽象方面
public abstract aspect A {
abstract pointcut publicCall(int i);
}
清單 2 繼承抽象方面的子方面
public aspect B extends A {
pointcut publicCall(int i): call(public Foo.m(int)) && args(i);
}
AspectJ對繼承和抽象的支持正是我們構造方面庫的基礎。抽象方面包含抽象 的切入點和具體的通知,正符合方面庫的特征,可以使用抽象方面來構造方面庫 文件。繼承抽象方面的子方面必須具體化切入點,可以把它當作方面庫在具體應 用程序中的實施。為了加深理解,我們將在接下來的章節中使用抽象方面技術制 作一個簡單的記錄蹤跡的方面庫,並擴展它以應用到具體項目中。
從AspectJ 5開始支持的注釋是另外一種構造方面庫的技術基礎。Java5引入了 注釋這種類型,它以注釋的形式來表達程序中各成員的元數據信息,采用符號@標 示。 Java5中可以被注釋修飾的Java程序成員有很多,AspectJ 5能支持的注釋包 括修飾方面、方法、屬性、構造函數和通知,修飾方法和通知的參數的注釋也能 支持,但是不支持pointcut和declare語句上的注釋。為了支持注釋類型, AspectJ 5擴展了pointcut語法,可以匹配存在或者不存在的注釋類型。例如清單 3中的名叫onewayMethod的pointcut可以匹配所有被注釋@Oneway修飾的方法調用 。
清單 3 含有注釋的pointcut
public aspect C {
pointcut onewayMethod: call(@Oneway * *(..));
}
AspectJ 5對注釋的支持簡化了實施庫的方法,我們可以很容易地想到,在把 方面庫實施到應用程序時,可以利用注釋標明具體的切入點的位置。在構造方面 庫文件時,只需要定義好與注釋相關的切入點,並規定該切入點上的具體的通知 內容就可以了。我們同樣會在下面的章節中介紹使用注釋制作簡單的方面庫的過 程。
3.使用抽象方面構造方面庫
曾經有一段時間,我們只有在獲得了所有源代碼的情況才才能順利的編譯用到 了AspectJ的軟件項目。現在情況當然已經和以往大大不同,由於 AspectJ今年來 的迅速發展,在項目中使用AspectJ的要求大大放松了。我們可以在沒有源代碼的 情況下進行AOP開發。無論是程序主邏輯部分還是 Aspect部分,都可以用jar文件 的形式提供,並且能夠順利的通過AspectJ的編譯。
下面我們將通過一個例子演示如何在AJDT中構造Aspect庫。在構建這個例子的 過程中,我們用到了Eclipse及其上的AJDT插件。這兩個工具可以分別從 http://www.eclipse.org 和http://www.eclipse.org/ajdt 上下載。AJDT從多個 方面擴展了Eclipse的Java開發環境使其支持面向方面的編程。當在Eclipse平台 上安裝了AJDT插件之後,我們現在能新建AspectJ項目和創建新的Aspect了(圖 1 )。
圖 1 AJDT提供的Wizard
利用圖 1中所示的向導,我們建立一個AspectJ Project來存放我們的方面庫 。我們將這個工程命名為sample.aspects.library。隨後,我們可以利用AJDT提 供的工具按鈕建立一個新的Aspect, 用到的工具欄按鈕如圖 2所示。
圖 2 使用工具欄按鈕建立一個新的Aspect
通過AJDT提供的向導,我們建立了如下的Aspect:
清單 4 抽象方面AbstractTrace
package sample.aspects.library;
public abstract aspect AbstractTrace {
public abstract pointcut TraceScope();
Object around():TraceScope(){
Object sig = thisJoinPoint.getSignature();
System.out.println("Enter "+sig);
Object res = proceed();
System.out.println("Exit "+sig);
return res;
}
}
清單 4中建立了一個名為AbstractTrace的抽象方面,它包含了一個pointcut 和一個advice。我們看到,名為TraceScope的 pointcut也被標明是抽象的。根據 AspectJ的規范,一個抽象的方面是不會起作用的。抽象方面存在的唯一目的是為 了被用戶繼承。接下來,我們將整個工程導出成為一個.jar文件。在AspectJ中, 我們可以將這種形式的Jar交給用戶,它們隨後就可以利用繼承來使用方面庫提供 的功能了。
如果我們希望在一個新的工程中使用方面庫,我們需要在工程中指定用到的方 面庫,並繼承方面庫中提供的抽象方面。在Eclipse中,我們首先將這個 jar文件 加入到工程的"Java Build Path"中,就如同使用一個普通的Java類庫一樣。隨後 ,我們就可以利用AJDT提供的向導來繼承抽象方面了,如圖 3所示。
圖 3 繼承抽象Aspect
在圖 3中,我們可以指定在新的Aspect中實現所有的抽象pointcut,這樣能讓 AJDT幫助我們多做一些工作。最後生成並修改後的代碼如清單 5所示,它具體化 了抽象pointcut,指明這個方面庫應用的范圍,即切入點是任何函數執行的時刻 。
清單 5 繼承抽象Aspect
package aspects;
import sample.aspects.library.AbstractTrace;
public aspect Trace extends AbstractTrace {
public pointcut TraceScope():execution(* *(..));
}
從清單 5中我們可以看出,通過使用已有的方面庫,用戶需要編寫的代碼變得 非常簡潔。商務邏輯的開發人員可以不去了解AspectJ的一些高級用法,他們只需 要指定哪些時候需要用到方面中提供的功能就可以了。
4.使用注釋構造方面庫
前面介紹了使用抽象方面構造一個記錄蹤跡的方面庫並實施到具體應用中,這 章我們采用注釋技術構造一個方面庫,以完成同樣的功能。
與前面一章相同,首先創建一個AspectJ的工程,工程名為 sample.annotation.library。需要注意的是,因為要在工程中使用注釋類型,所 以JDK依從的版本必須為5.0。如果你目前的缺省設置不是5.0,請如圖所示在JDK Compliance中選擇"為工程指定一個",設置為5.0。或者在工程的properties對話 框中選擇Java Compiler頁面修改。
圖 4 修改JDK Compilance
接下來要做的就是新建一個標准Java注釋類型,在Eclipse中點擊File- >New->Other彈出New對話框,選擇 Java下面的Annotation,設定它的 Package為sample.annotation.library,名稱叫做trace,其他接受缺省值,點擊 完成。這樣,一個名叫trace的注釋類型就被創建出來了,我們將基於它創建一個 記錄蹤跡的方面庫。
接下來創建方面庫中的Aspect,即一個aj文件,文件如清單 6所示。
清單 6 捕捉注釋的Aspect
package sample.annotation.library;
public aspect AnnotationTrace {
pointcut traceScope():execution(@trace * *(..));
Object around():traceScope(){
Object sig = thisJoinPoint.getSignature();
System.out.println("Enter "+sig);
Object res = proceed();
System.out.println("Exit "+sig);
return res;
}
}
這樣,方面庫的編碼工作就完成了,與上一個例子類似,我們把它打包成 tracelib.jar文件備用。
現在我們來介紹如何應用方面庫tracelib.jar。創建一個新的AspectJ項目 sample.annotation.application,注意,因為要使用注釋類型,修改JDK Compliance為5.0。新建一個標准Java類business.logic.MainLogic,添加簡單函 數testTrace(),用這個 Java類來模擬應用程序。
為了使用方面庫,首先必須將方面庫的jar文件添加到AspectJ Aspect Path中 ,在工程的properties對話框中選擇AspectJ Aspect Path頁面添加方面庫的jar 文件,如圖 5所示。在應用程序MainLogic類中使用記錄蹤跡的功能時,只需要簡 單的將@trace添加到想要使用這個功能的函數前面即可。MainLogic 類的源碼如 清單 7所示。將MainLogic類作為AspectJ/Java Application來運行,可以看到輸 出結果,在testTrace()調用之前和之後,加入了記錄蹤跡的語句,如清單 8所示 。這樣就達到了我們使用方面庫tracelib.jar的目的。相比較而言,通過注釋技 術實現的方面庫使用起來是更為便捷。
圖 5 添加AspectJ Aspect Path
清單 7 應用程序MainLogic源碼
package business.logic;
import sample.annotation.library.trace;
public class MainLogic {
@trace
public static void testTrace(){
System.out.println("call testTrace function.");
}
public static void main(String[] args) {
MainLogic.testTrace();
}
}
清單 8 MainLogic輸出結果
Enter void business.logic.MainLogic.testTrace()
call testTrace function.
Exit void business.logic.MainLogic.testTrace()
5.總結
與OOP中的可重用庫類似,AOP方面庫可以在整個工程或者多個工程中重復使用 ,從而提高程序編寫的准確性和方便性。使用方面庫的編程人員只需要了解簡單 的AOP知識就能享受到AOP帶來的好處,把方面庫實施到具體的應用中。本文在介 紹方面庫基本概念的基礎上,介紹了利用AspectJ構造方面庫的兩種技術,希望能 夠對AOP在應用項目中廣泛應用有所幫助。