第二章 AspectJ語言<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
引語
在本系列的前一章中,我們簡要的說明了AspectJ語言的總攬。爲了理解AspectJ的語法和語義,你應該閱讀本章。這一部分包括了前述的一些材料,但是將更加完整和更多的討論細節。文章將由一個具體方面的例子開始,這個方面包括了一個切點,一個類型間聲明和兩個通知,這個例子將給我們一些討論的話題。
分析方面(The Anatomy of an Aspect)
首先給出我們定義的方面。
1 aspect FaultHandler {
2
3 private boolean Server.disabled = false;
4
5 private void reportFault() {
6 System.out.println("Failure! Please fix it.");
7 }
8
9 public static void fixServer(Server s) {
10 s.disabled = false;
11 }
12
13 pointcut services(Server s): target(s) && call(public * *(..));
14
15 before(Server s): services(s) {
16 if (s.disabled) throw new DisabledException();
17 }
18
19 after(Server s) throwing (FaultException e): services(s) {
20 s.disabled = true;
21 reportFault();
22 }
23 }
FaultHandler包括一個在Server上的類型間字段聲明(第3行),兩個方法(5-7行和9-11行),切點定義(13行),兩個通知(15-17行和19-22行)。這些覆蓋了方面能夠包括的基本信息。通常來說,方面包括其他程序實體、不同的變量和方法、切點定義、類型間聲明和通知(可能有before、after或around)。文章的餘下部分將逐一討論這些橫切相關的構造。
切點(Pointcuts)
AspectJ的切點定義爲切點取名。切點自己捕捉連接點集合,例如,程序執行中的趕興趣的點集合。這些連接點可以是方法或者構造子的調用或執行,異常處理,字段的賦值和讀取等等。舉個例子,在13行的切點定義:
pointcut services(Server s): target(s) && call(public * *(..))
這個切點,被命名爲services,捕捉當Server對象的公共方法被調用時程序執行過程中的連接點。它同樣允許使用services切點的任何人訪問那些方法被調用的Server對象。FaultHandler方面的這個切點背後的思想是指錯誤處理相關的行爲必須由公共方法的調用來觸發。例如,server可能因爲某些錯誤不能處理請求。因此,對那些方法的調用是該方面感興趣的事件,當這些事件發生時,對應的錯誤相關的事情也將發生。
事件發生時的部分上下文由切點的參數暴露。在這個例子中,就是指Server類型的對象。這個參數在切點聲明的右邊被使用,以便指明哪個事件與切點相關。在例子中,services切點包括兩部分,它是由捕捉那些以Server爲目標對象的操作(target(S))的連接點,與那些捕捉call的連接點(call(..))組合起來(&&,意爲邏輯與and)形成的切點。調用連接點(calls)通過方法簽名描述,在此例中,使用了幾個通配符表達。在返回類型的位置是(*),在方法名的位置是(*)而在參數列表位置是(..);只指定了一個具體信息,就是public。
切點捕捉程序中的大量連接點。但是它們僅僅捕捉幾種連接點。這些種類的連接點代表了Java語言中的一些重要概念。這裏是對於這些概念的不完整列表:方法調用、方法執行、異常處理、實例化、構造子執行以及字段的訪問。每一種連接點都由特定的切點捕捉,你將在本文的其他部分學到它們。
通知(Advice)
一個通知由切點和通知代碼組成,它定義了在切點捕捉到的連接點處運行的邏輯。例如,15-17行的通知中的代碼if (s.disabled) throw new DisabledException();當Server的實例調用它的公共方法時執行。19-22行定義的另一個通知在同一個切點(services)處執行。
{
s.disabled = true;
reportFault();
}
當時第二個通知只有在程序拋出FaultException時才執行。有三類的after通知:分別基於方法正常結束、方法異常結束和方法一任何方式結束。
連接點和切點(Join Points and Pointcuts)
考慮下面的Java類
class Point {
private int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
void setX(int x) { this.x = x; }
void setY(int y) { this.y = y; }
int getX() { return x; }
int getY() { return y; }
}
爲了能夠理解AspectJ的連接點和切點概念,讓我們回顧以下Java語言的一些基本原理。考慮類Point中的方法聲明 void setX(int x) { this.x = x; } 這個程序片段說明當Point類實例調用名爲 setX有一個整型參數的方法時,程序執行方法體{ this.x=x;}。與此類似的是,類的構造子表明如果當Point類使用兩個整型參數實例化時,構造子體內的{this.x=x;this.y=y;}將被執行。用一句話總結就是:當一些事發生時,就有一些東西被執行。在面向對象程序中,有一些種類的“發生的事”是由語言本身所決定。我們把這些稱爲Java的連接點。連接點有一些像方法調用、方法執行、對象實例化、構造子執行、字段引用以及異常處理等組成。
而切點就是用來捕捉這些連接點的結構,例如,下面的切點
pointcut setter(): target(Point) &&
(call(void setX(int)) ||
call(void setY(int)));
捕捉對於Point實例上setX(int)或setY(int)的每一個方法調用。看看另外一個例子
pointcut ioHandler(): within(MyClass) && handler(IOException);
這個切點捕捉類MyClass內異常處理代碼執行時的每個連接點。
切點定義包括由冒號分割的兩部分。左邊包括切點的名稱和切點的參數(例如事件發生時的數據)。右邊則包括切點本身。
一些切點的例子
下面的切點是特定方法執行時起作用
execution(void Point.setX(int))
進行方法調用時則使用
call(void Point.setX(int))
異常處理執行時的切點定義如下
handler(ArrayOutOfBoundsException)
當前正在使用SomeType類型的對象
this(SomeType)
SomeType類型對象爲目標對象時
target(SomeType)
如果連接點處在Test的無參數main函數調用流程中
cflow(call(void Test.main())
切點還可以使用或(“||”)以及與(“and”)和非(“!”)組合。
·可以使用通配符。因此
1. Execution(* *(..))
2. Call(* set(..))
代表(1)不考慮參數和返回值的任何方法執行(2)對於任何參數和返回值的方法名爲set方法的調用。
·可以基於類型選擇元素,例如
1. Execution(int *())
2. Call(* setY(long))
3. Call(* Point.setY(int))
4. Call(*.new(int,int))
代表(1)返回值是int型的任何無參數方法執行;(2)任何返回類型且參數爲long類型的名爲setY方法的調用;(3)任意Point對象的有一個int類型setY方法的調用,忽略返回類型;(4)對於任何類的構造子的調用,只要該構造子有兩個int類型的參數。
·如何組合切點,例如
1. Target(Point) && call(int *())
2. Call(* *(..)) && (within(Line) || within(Point))
3. Within(*) && execution(*.new(int))
4. !this(Point) && call(int *(..))
代表(1)Point實例上返回類型爲int的任意無參數方法調用;(2)Line或Point定義及其實例產生的任意方法調用;(3)任意帶一個int類型參數的構造子調用;(4)任意返回類型爲int類型的方法調用只要當前執行類實例不是Point類型。
·選擇有特定修飾的方法或構造子
1. Call(public * *(..))
2. Execution(!static * *(..))
3. Execution(public !static * *(..))
代表(1)任意公共方法的調用;(2)任意非靜態方法的執行;(3)任意公共非靜態方法的執行。
·切點還能夠處理接口。例如給定接口
interface MyInterface { … }
切點call(* MyInterface.*(..))捕捉MyInterface定義的方法以及超類型定義的方法調用。
切點合成
切點可以使用操作符與(&&)、或(||)和非(!)。這樣可以使用簡單的原始切點來創建強大功能的切點。當使用原始切點cflow和cflowbelow進行組合時,可能會有些迷糊。例如,cflow(p)捕捉p流程內(包括P在內)的所有連接點,可以使用圖形表示
P ---------------------
/
/ cflow of P
/
那麼cflow(P) && cflow(Q)捕捉的是什麼呢?它捕捉同時處於P和Q流程中的連接點。
P ---------------------
/
/ cflow of P
/
/
/
Q -------------/-------
/ /
/ cflow of Q / cflow(P) && cflow(Q)
/ /
注意P和Q可能沒有任何公共的連接點,但是它們的程序流程中可能有公共的連接點。
Cflow(P && Q)又是什麼意思呢?它的意思是被P和Q共同捕捉的連接點的流程。
P && Q -------------------
/
/ cflow of (P && Q)
/
如果P和Q沒有捕捉到公共的連接點,那麼在(P&&Q)的程序流程中不可能有任何連接點。下面代碼表明上述意思
public class Test {
public static void main(String[] args) {
foo();
}
static void foo() {
goo();
}
static void goo() {
System.out.println("hi");
}
}
aspect A {
pointcut fooPC(): execution(void Test.foo());
pointcut gooPC(): execution(void Test.goo());
pointcut printPC(): call(void java.io.PrintStream.println(String));
before(): cflow(fooPC()) && cflow(gooPC()) && printPC() {
System.out.println("should occur");
}
before(): cflow(fooPC() && gooPC()) && printPC() {
System.out.println("should not occur");
}
}
切點參數
考慮下述代碼
pointcut setter():
target(Point) && (call(void setX(int)) || call(void setY(int)));
這個切點捕捉以Point實例爲目標的每個setX(int)或setY(int)方法的調用。切點名爲setters並且在左邊沒有任何參數。空的參數列意味着切點不會暴露任何連接點的上下文信息。但是考慮另一版本的切點定義
pointcut setter(Point p):
target(p) && (call(void setX(int)) || call(void setY(int)));
它的功能與前述的切點相同,但是這裏的切點包括一個參數類型爲Point。這就意味着任何使用這個切點的通知都可以訪問被切點捕捉連接點中的Point實例。
現在看看又一版本的setters切點
pointcut setter(Point p, int newval): target(p) && args(newval)
&& (call(void setX(int)) || call(void setY(int)));
這裏切點暴露了一個Point對象和一個int值。在切點定義的右邊,我們發現Point對象是目標對象,而且int值是被調用方法的參數。
切點參數的使用有很大的伸縮性。最重要的規則是所有的切點參數必須被綁定在每個切點捕捉的連接點上。因此,下面的例子就會參數編譯錯誤。
pointcut badPointcut(Point p1, Point p2):
(target(p1) && call(void setX(int))) ||
(target(p2) && call(void setY(int)));
因爲p1僅當setX調用時被綁定,而 p2只在setY調用時被綁定,但是切點卻捕捉所有的這些連接點並且試圖同時綁定p1和p2
一個例子:HandleLiveness
這個例子包括兩個對象類,一個異常類和一個方面。Handle對象僅僅是代理Partner對象的非靜態公共方法。方面HandleLiveness確保在代理之前Partner存在並且可用,否則拋出一個異常。
class Handle {
Partner partner = new Partner();
public void foo() { partner.foo(); }
public void bar(int x) { partner.bar(x); }
public static void main(String[] args) {
Handle h1 = new Handle();
h1.foo();
h1.bar(2);
}
}
class Partner {
boolean isAlive() { return true; }
void foo() { System.out.println("foo"); }
void bar(int x) { System.out.println("bar " + x); }
}
aspect HandleLiveness {
before(Handle handle): target(handle) && call(public * *(..)) {
if ( handle.partner == null || !handle.partner.isAlive() ) {
throw new DeadPartnerException();
}
}
}
class DeadPartnerException extends RuntimeException {}
更多信息
如果需要轉貼請寫名作者和出處。