1. 程式人生 > >AspectJ——切入點語法(4)之捕獲屬性上的連線點

AspectJ——切入點語法(4)之捕獲屬性上的連線點

捕獲屬性上的連線點

AspectJ提供了get(Signature)set(Signature)切入點的形式,來捕獲可能發生在類屬性上的任何訪問和修改。這也是AspectJ的一個比較受爭議的特性,因為它會有效地破壞類的封裝性,特別是當把被監視的屬性宣告為protected或者private時。

所以這兩個屬性可以提供強大的手段來通知類,但是必須小心地使用它們。

0.捕獲對屬性的訪問

我們使用get(Signature)切入點來捕獲對物件屬性的訪問。切入點的語法如下:

pointcut [切入點名字](想要捕獲的引數): get(<可選的修飾符> 屬性型別 類名.屬性名)

需要注意的幾點是:

  1. get(Signature)切入點會捕獲對屬性的直接訪問,不僅僅只會捕獲對屬性getter訪問器方法的呼叫。
  2. get(Signature)切入點不能捕獲對常量的訪問。
  3. Signature必須解析成特定類的屬性。
  4. 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,然後又定義兩個普通的變數firstnamelastname,並且為兩個普通的變數提供了gettersetter方法。

接著我們定義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無法捕獲對常量的訪問。

而我們對firstnamelastname屬性的訪問是通過其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(<可選的修飾符> 屬性型別 類名.屬性名)

需要注意的幾點是:

  1. set(Signature)切入點在修改屬性時觸發。
  2. Signature必須解析成特定類的屬性。
  3. 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屬性。接著呼叫firstnamelastnamesetter方法。

接著新增切面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);
    }
}

此時的執行結果是:

這裡寫圖片描述