說說 Spring 支援的 AspectJ 切點函式

deniro發表於2018-08-12

1 @annotation()

它表示標註了某個註解的所有方法。

假設有一個介面 Cook,它有兩個實現類 CookA、CookB:

說說 Spring 支援的 AspectJ 切點函式

Cook:

public interface Cook {

    /**
     * 製作食品
     */
    void make();

}
複製程式碼

CookA:

public class CookA implements Cook {
    public void make() {
        System.out.println("製作食品");
    }
}
複製程式碼

CookB:

public class CookB implements Cook {
    @Log
    public void make() {
        System.out.println("製作糕點");
    }
}
複製程式碼

我們在 CookB 的 make() 方法上加了 @Log 註解。

@Log 註解:

@Retention(RetentionPolicy.RUNTIME)//保留期限
@Target(ElementType.METHOD)//目標型別
public @interface Log {
    boolean value() default true;//宣告成員變數
}
複製程式碼

現在使用 @annotation() 來為所有加了 @Log 註解的方法織入增強:

@Aspect
public class AnnotationAspect {

    @AfterReturning("@annotation(net.deniro.spring4.anno.Log)")
    public void log() {
        System.out.println("新增日誌");
    }
}
複製程式碼

單元測試:

ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Cook cookA = (Cook) context.getBean("cookA");
cookA.make();
Cook cookB = (Cook) context.getBean("cookB");
cookB.make();
複製程式碼

輸出結果:

製作食品 製作糕點 新增日誌

從輸出結果中可以看出,配置了 @Log 註解的方法織入了增強。

2 execution()

它是最常用的切點函式,具體語法如下:

execution(<修飾符模式>?<返回型別模式><方法名模式>(<引數模式>)<異常模式>?)

假設廚師介面 Cook,存在下面這樣的類關係:

說說 Spring 支援的 AspectJ 切點函式

CookA 除了會做菜與管理外,還會清潔的技能;CookB 也具有稽核的獨門技能。

2.1 方法簽名定義切點

表示式示例 說明
execution(public * *(..)) 表示匹配所有目標類的 public 方法 。 第一個 * 代表任意返回型別,第二個 * 代表方法名,而 .. 代表任意入參的方法。
execution(* m*(..)) 表示匹配目標類所有以 m 為字首的方法。 第一個*代表任意返回型別,而m*代表任意以 m 為字首的方法。這裡匹配 make() 與 manage() 方法。

2.2 類定義切點

表示式示例 說明
execution(* net.deniro.spring4.aspectj.Cook.*(..)) 匹配 Cook 介面的所有方法,即這裡的 make() 與 manage() 方法。第一個*代表任意返回型別, net.deniro.spring4.aspectj.Cook.* 表示 Cook 介面的所有方法。
execution(* net.deniro.spring4.aspectj.Cook+.*(..)) 匹配 Cook 介面及其所有實現類的方法,它不但匹配介面中定義的 make() 與 manage() 方法,還匹配它們各自的 clean() 與 audit() 方法。

2.3 類包定義切點

類名模式 說明
.* 包中的所有類,不包括子孫包中的類。
..* 包中以及子孫包的所有類。
表示式示例 說明
execution(* net.deniro.*(..)) 匹配 net.deniro 包中所有類的所有方法。
execution(* net.deniro..*(..)) 匹配 net.deniro 包以及子孫包中所有類的所有方法。

2.4 方法入參定義切點

引數中的萬用字元 說明
* 表示任意型別引數。
.. 表示任意型別引數且引數不限個數。
表示式示例 說明
execution(* make(int,String)) 這會匹配 make(int,String) 方法。如果方法中的入參型別是 java.lang 包下的類,那麼可以直接使用類名;如果是其他型別,那麼必須使用全限定名。
execution(* make(int,*)) 匹配 make() 方法,該方法的第一個入參是 int,第二個引數可以是任意型別。
execution(* make(int,..)) 匹配 make() 方法,該方法的第一個入參是 int,後面可以有任意型別的入參且引數個數不限。
execution(* make(Object+)) 匹配 make() 方法,方法只有一個入參,它可以是 Object 型別或該型別的子類。

3 args() 與 @args()

3.1 args()

它接收一個類名,表示目前方法的入參物件(執行時)是指定類或子類時,就匹配切點。

表示式示例 說明
args(net.deniro.CookA) 表示執行時入參型別為 CookA。
execution(* *(net.deniro.CookA)) 表示方法簽名中的入參型別為 CookA。

3.2 @args()

它接收一個註解類的類名,當方法的執行時入參物件標註了指定註解時,就匹配切點。

假設存在如下繼承結構的類關係:

說說 Spring 支援的 AspectJ 切點函式

CookB 的方法簽名為 method(CookB cook),我們稱之為入參型別點。標註了@args(Log) 註解的類,我們稱之為註解點

  1. 如果註解點高於入參型別點,那麼入參型別點的類及其子孫類,都不匹配該切點@args(Log)

註解點高於入參型別點

  1. 如果註解點低於入參型別點,那麼入參型別點的子孫類,都匹配該切點@args(Log)

註解點低於入參型別點

4 within()

within() 定義的連線點最小範圍是類(不是介面),而 execution() 定義的連線點最小範圍可以到方法入參,所以 execution() 涵蓋了 within() 功能。

within() 語法:within (<類匹配模式>)

表示式示例 說明
within(net.deniro.CookA) 匹配目標類 CookA 中的所有方法。
within(net.deniro.*) 匹配 net.deniro 包中所有的類,但不包括子孫包中的類。
within(net.deniro..*) 匹配 net.deniro 包及其子孫包中所有的類。

5 @within() 與 @target()

這兩個註解的入參只能是類名(非介面)。

  • @within(Log):匹配標註了 @Log 的類及其子孫類。
  • @target(Log):匹配標註了 @Log 的類。

說說 Spring 支援的 AspectJ 切點函式

示例中的 CookB 標註了 @Log 註解,那麼 @target(Log) 只匹配 CookB;而 @within(Log) 匹配 CookB 及其子孫類(示例中的 CookC 與 CookD)。

6 target() 與 this()

target() 會根據目標類的型別來確定是否匹配; this() 會根據代理類的型別來確定是否匹配。它們都僅接受類名作為引數。

6.1 target()

target(C) 表示如果目標類的型別是 C時,那麼目標類的所有方法都匹配切點。

表示式示例 說明
target(net.deniro.Cook) Cook 的所有實現類及其子孫類中的所有方法(包括未在 Cook 介面中定義的方法)都匹配切點。
target(net.deniro.Cook+) target(net.deniro.Cook) 等價。

6.2 this()

一般來說, this() 與 target() 是等效的。但在由引介切面產生的代理物件場景中,卻表現不同。

我們定義了一個引介切面,讓 Cook 擁有 Designer 的技能 O(∩_∩)O哈哈~

Designer 介面:

public interface Designer {
    //設計
    void design();
}
複製程式碼

DefaultDesigner 實現類:

public class DefaultDesigner implements Designer {
    public void design() {
        System.out.println("開始設計啦");
    }
}
複製程式碼

DesignerAspect 切面:

@Aspect
public class DesignerAspect {

    @DeclareParents(value = "net.deniro.spring4.aspectj.CookA",defaultImpl =
            DefaultDesigner.class)
    public static Designer designer;
}
複製程式碼

接著,再定義了一個使用 this 語法的後置增強切面:

@Aspect
public class ThisAspect {

    /**
     * 為任何執行期物件為 Designer 型別的 Bean 織入後置增強
     */
    @AfterReturning("this(net.deniro.spring4.aspectj.Designer)")
    public void test() {
        System.out.println("執行 ThisAspect");
    }
}
複製程式碼

Spring 配置:

<!--aspectj 驅動器 -->
<aop:aspectj-autoproxy/>

<bean id="cookA" class="net.deniro.spring4.aspectj.CookA"/>
<bean class="net.deniro.spring4.aspectj.DesignerAspect"/>
<bean class="net.deniro.spring4.aspectj.ThisAspect"/>
複製程式碼

輸出結果:

製作食品 執行 ThisAspect 開始設計啦

因為 CookA 被織入了 Designer 介面,所以複合 this 定義的切面,所以 ThisAspect 中的方法被執行啦O(∩_∩)O哈哈~


CookA 通過引介增強了 Designer 介面,那麼 this(net.deniro.spring4.aspectj.Designer) 會匹配 CookA 中的所有方法以及 Designer 介面定義的方法。而 target(net.deniro.spring4.aspectj.Designer) 只會匹配Designer 介面定義的方法。

相關文章