AspectJ——切入點語法(4)之捕獲屬性上的連線點
捕獲屬性上的連線點
AspectJ提供了get(Signature)
和set(Signature)
切入點的形式,來捕獲可能發生在類屬性上的任何訪問和修改。這也是AspectJ的一個比較受爭議的特性,因為它會有效地破壞類的封裝性,特別是當把被監視的屬性宣告為protected
或者private
時。
所以這兩個屬性可以提供強大的手段來通知類,但是必須小心地使用它們。
0.捕獲對屬性的訪問
我們使用get(Signature)
切入點來捕獲對物件屬性的訪問。切入點的語法如下:
pointcut [切入點名字](想要捕獲的引數): get(<可選的修飾符> 屬性型別 類名.屬性名)
需要注意的幾點是:
get(Signature)
切入點會捕獲對屬性的直接訪問,不僅僅只會捕獲對屬性getter
訪問器方法的呼叫。get(Signature)
切入點不能捕獲對常量的訪問。Signature
必須解析成特定類的屬性。Signature
可以包含萬用字元,用於選擇不同屬性上的一系列連線點。
我們在Test7
包下做一個簡單的測試。首先新建業務類Service
:
package Test7;
public class Service {
protected static String name = "Gavin John";
public static final String nickname = "GG";
private String firstname = "Gavin";
private String lastname = "John";
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
可以看到,在Service
類中,我們首先定義了靜態的name
屬性,接著使用static final
定義了常量nickname
,然後又定義兩個普通的變數firstname
和lastname
,並且為兩個普通的變數提供了getter
和setter
方法。
接著我們定義Main
主方法類,在主方法中訪問這些變數:
package Test7;
public class Main {
public static void main(String[] args) {
Service service = new Service();
System.out.println("service.getFirstname(): " + service.getFirstname());
System.out.println("service.getLastname(): " + service.getLastname());
System.out.println("Service.name: " + Service.name);
System.out.println("Service.nickname: " + Service.nickname);
}
}
如果我們不新增切面,此時的執行結果顯而易見,如下:
接著,我們新增切面FieldAspect
,如下:
package Test7;
public aspect FieldAspect {
pointcut getNamePointcut(): get(String Service.*name);
before():getNamePointcut(){
System.out.println();
System.out.println("Signature: " + thisJoinPoint.getSignature());
System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
}
}
在該切面中,我們使用get(Signature)
,並通過萬用字元定義了訪問Service
類中的以name
結尾的屬性切入點。
此時執行結果如下:
從執行結果中可以看出,除了對nickname
屬性的訪問之外,對其他屬性的訪問都被捕獲到。nickname
是一個通過static final
定義的常量,因為編譯器對常量的編譯特性,AspectJ無法捕獲對常量的訪問。
而我們對firstname
和lastname
屬性的訪問是通過其getter
方法,而對name
靜態屬性訪問是直接通過屬性名字訪問,這兩種訪問形式都被切入點捕獲到。
1.獲取訪問的屬性值
我們不僅可以捕獲對屬性的訪問,還能獲取所訪問的屬性值。這就要通過after() returning(<ReturnValue>)
形式的通知,它在宣告的returning()
部分中帶有一個識別符號,用於包含訪問過的值。
比如,我們簡單修改上例中的切面,將其中的before()
前置通知,改為after() returning(<ReturnValue>)
形式的後置通知,如下:
package Test7;
public aspect FieldAspect {
pointcut getNamePointcut(): get(String Service.*name);
after() returning(String value):getNamePointcut(){
System.out.println();
System.out.println("Signature: " + thisJoinPoint.getSignature());
System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
System.out.println("訪問的屬性值是:" + value);
}
}
此時執行結果是:
可以看到,我們在通知中獲取到了訪問的屬性值。
2.捕獲對屬性的修改
我們使用set(Signature)
切入點來捕獲對物件屬性的修改。其語法是:
pointcut [切入點名字](要獲取的引數): set(<可選的修飾符> 屬性型別 類名.屬性名)
需要注意的幾點是:
set(Signature)
切入點在修改屬性時觸發。Signature
必須解析成特定類的屬性。Signature
可以包含萬用字元,用於選擇不同屬性上的一系列連線點。
我們在Test8
中做一個簡單的測試。Test8
包結構與上面的Test7
一樣。
Service
類與上例中一樣,這裡不再贅述。測試類Main
中,我們將對屬性的訪問修改為對屬性的修改,如下:
package Test8;
public class Main {
public static void main(String[] args) {
Service service = new Service();
Service.name = "Gavin0 John0";
service.setFirstname("Gavin0");
service.setLastname("John0");
}
}
首先我們直接通過屬性名字修改name
屬性。接著呼叫firstname
和lastname
的setter
方法。
接著新增切面FieldAspect
,如下:
package Test8;
public aspect FieldAspect {
pointcut setNamePointcut(): set(String Service.*name);
before(): setNamePointcut(){
System.out.println();
System.out.println("Signature: " + thisJoinPoint.getSignature());
System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
}
}
切入點setNamePointcut
使用set(Singature)
和萬用字元定義了對Service
類中以name
結尾的屬性的修改切入點。
執行程式,結果如下:
可以看到,切面捕獲了對三個屬性的修改,但是奇怪的是每個屬性都修改了兩遍,這是為什麼呢?其實不難理解,在Service
類中,我們對這三個屬性的第一次賦值,也算是一次修改,這也被切面捕獲了。
3.在修改屬性時獲取賦給它的值
假如我們想在通知中獲取,修改屬性的時候賦給屬性的值,該怎麼做呢?這可以結合我們前面用過的args
原生切入點,獲取給其傳遞的引數即可。
如上例,我們將切面修改如下:
package Test8;
public aspect FieldAspect {
pointcut setNamePointcut(String newValue): set(String Service.*name) && args(newValue);
before(String newValue): setNamePointcut(newValue){
System.out.println();
System.out.println("Signature: " + thisJoinPoint.getSignature());
System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
System.out.println("賦給屬性的值是:" + newValue);
}
}
此時的執行結果是: