??? ??? 讓我好好想想,
AspectJ
中最常用的切入點是什么?哦,也許是
call
(
Method-Signature
)吧。這是個相對簡單的方法簽名。實際上,方法簽名的完整形式如下:
[modifiers]
[returnTypePattern]
[DeclaredTypePattern.]methodName([Parameters])[throws
TypePattern]
,其中方括號中的簽名組件是可選的。
modifiers
為修飾符模式,
returnTypePattern
為返回類型模式,
DeclaredTypePattern
為類型聲明模式,
methodName
為方法名稱,
Parameters
為方法參數,
throws
TypePattern
為
throw
字句。該文僅僅介紹
DeclaredTypePattern
,因為相比之下其它模式比較簡單的多。
??? ??? 在介紹類型聲明模式之前,介紹一下類型模式。類型模式是匹配一種類型或一系列類型的方法。精確的類型模式是如
java.lang.String
一樣的完整的有效類型名。但在使用
AspectJ
類型模式時,經常會用到下列通配符(這些通配符同樣適用于
Spring
的
AOP
)。
1
)“
*”
:代表任意字符的零次或多次出現。當嵌入到一串字符的內部時,它匹配任意字符的零次或多次出現,除了包分割符(
.
)。
2
)“
+”
:用作類型模式的后綴,代表此類型和其所有的子類型(那些擴展或實現帶后綴類型的類型)。
3
)“
..”
:用于指定所有的子包,它匹配任意以包分割符開頭和結束的字符串。
?
下面給出幾個示例:
1
)
*Account
使用
Account
名稱結束的類型,如
CheckingAccount
2
)
java.*.Date
類型
Date
在任何直接的
java
子包中,如
java.util.Date
和
java.sql.Date
3
)
java..*
任何在
java
包或者所有子包中的類型,如
java.awt
或者
java.awt.event
4
)
javax..*Model+
所有
javax
包或者子包中以
Model
結尾的類型和其所有子類,如
TableModel,TreeModel
。
??? ??? 現在開始說說類型聲明模式。實際上,在方法簽名中,類型聲明模式不是必需的(就像很多書中所說,應該少用類型聲明模式而改用與“
target”
結合的切入點指示符)。但如果指定了類型聲明模式,切入點將只匹配對由模式匹配的類型(或者超類型)聲明的方法的調用。和其他類型模式一樣,類型聲明模式支持上述的通配符。同時,它也支持復合類型模式。對于類型聲明模式來說,程序員容易犯錯的地方在于類型聲明模式是基于靜態類型而不是運行時類型,這也是本文的主要內容。
? ??
在很好地理解類型聲明模式之前,先看一下下面的例子:
public
?
class
?A??{
????
public
?
void
?foo(){
????????System.out.println(
"
A.foo()
"
);
????}
}
public
?
class
?B?
extends
?A{
????
public
?
void
?foo(){
????????System.out.println(
"
B.foo()
"
);
????}
}
public
?
class
?Main?{
????
????
public
?
static
?
void
?main(String[]?args)?{
????????A?b?
=
?
new
?B();
????????b.foo();?
//
(1)
????????callFoo(b);
//
(2)
????}
????
public
?
static
?
void
?callFoo(A?a){
????????System.out.println(
"
Call?A
"
);
????}
????
????
public
?
static
?
void
?callFoo(B?b){
????????System.out.println(
"
Call?B
"
);
????}
}
??? ??? 它的運行結果是這樣的:
??? ??? B.foo()
??? ??? Call
A
??? ??? 和你的想法一致嗎?對于(
1
)處
b.foo()
的調用應用了面向對象中的覆蓋(
override
),它是動態的,是在運行時進行解析。而(
2
)處的
callFoo
(b)
則是重載(
overload
),它是靜態的,是在編譯時解析的。因此,對于變量
b
,雖然它是
B
的一個實例,但
b
的靜態類型(也就是變量聲明的類型)是
A
;由于重載方法的選擇是靜態的,所以
main
中調用的是
callFoo(A
a)
,而不是
callFoo(B
b)
。
??? ??? 終于說到了類型聲明模式。類型聲明模式是基于靜態類型信息進行匹配的,而不是動態(或者運行時。下面根據幾個典型的例子說明類型聲明模式的特性。
??? ??? 還是上面的兩個類
A
和
B
,現在我們定義一個方面如下:
public
?aspect?TypeAspect?{
????????pointcut?callA():
????????????call(
*
?A.
*
(..));
????????
????????before():callA(){
????????????System.out.println(
"
call?A
"
);
????????}????
}
??? ??? main
函數內容如下:
public
?
static
?
void
?main(String[]?args)?{
????????A?b1?
=
?
new
?B();
????????b1.foo();
????????
????????B?b2?
=
?
new
?B();
????????b2.foo();
????}
??? ??
運行結果如下:
call
A
B.foo()
call
A
B.foo()
??? ??? 可以看到,盡管切入點
callA()
聲明的類型為
A
,但實際上,切入點
callA()
可以捕獲
A
中的方法及其子類中繼承于
A
的方法或重載
A
的方法,而聲明的靜態類型既可以是
A
也可以是其子類。
??? ??? 但如果在
B
中增加一個新的方法:
public
?
void
?doAnotherThing(){
System.out.println(
"
B.doAnotherThing
"
);
}
??? ??? main
函數改為:
public
?
static
?
void
?main(String[]?args)?{
B?b2?
=
?
new
?B();
b2.doAnotherThing();
}
?? ??? 輸出結果為:
B.doAnotherThing
,如果想對
A
的子類
B
中擴展的方法進行通知,可采用的方法是將切入點
callA()
改為
pointcut
callA():
call
(*
A+.*(..));
。
??? ??? 讓我們再來看另一種情景:如果定義一個切入點如下:
?pointcut?callB():call(
*
?B.
*
(..));
before():callB(){
System.out.println(
"
call?B
"
);
}
??? ??? main
函數內容如下:
public
?
static
?
void
?main(String[]?args)?{
A?b?
=
?
new
?B();
b.foo();
}
??? ??? 運行結果為:
B.foo()
。
b.foo()
沒有匹配切入點
callB()
的原因在于,
b
的靜態類型是
A
,從靜態類型的角度來看,這是對
A
的調用,而不是對
B
的調用。在使用
AspectJ
的類型聲明時,很容易在這個地方犯錯。
??? ??? 好了,如上便是有關類型聲明模式的東西,說得有些凌亂,希望對
AspectJ
初學者有些幫助(我本身也是個初學者)。該文參考了《
Eclipse
AspectJ
》和《
AspectJ
cookbook
》。