1. 程式人生 > >說一說 Spring AOP 中 @Aspect 的高階用法

說一說 Spring AOP 中 @Aspect 的高階用法

1 切點複合運算

支援在切點定義中加入以下運算子進行復合運算:

運算子 說明 && 與運算。 ! 非運算。 || 或運算。 2 切點命名

一般情況下,切點是直接宣告在需要增強方法處,這種切點的宣告方式稱為匿名切點,匿名切點只能在宣告處被使用 。 如果希望在其它地方可以重用這個切點,我們可以通過 @Pointcut 註解及切面類方法來命名它。

public class NamePointcut {

/**
 * 切點被命名為 method1,且該切點只能在本類中使用
 */
@Pointcut("within(net.deniro.spring4.aspectj.*)")    private void method1() {
}    /**
 * 切點被命名為 method2,且該切點可以在本類或子孫類中使用
 */
@Pointcut("within(net.deniro.spring4.aspectj.*)")    protected void method2() {
}    /**
 * 切點被命名為 method3,且該切點可以在任何類中使用
 * 這裡還使用了複合運算
 */
@Pointcut("method1() && method2()")    public void method3() {
}

} 命名切點的結構如下:

切點可訪問性修飾符與類可訪問性修飾符的功能是相同的,它可以決定定義的切點可以在哪些類中可使用。

因為命名切點僅利用方法名及訪問修飾符的資訊,所以我們一般定義方法的返回型別為 void ,並且方法體為空 。

定義好切點後,就可以在切面類中引用啦:

@Aspectpublic class NamePointcutAspect { @After(“NamePointcut.method2()”) public void aspectMethod1() { } /** * 這裡使用了複合運算 */ @After(“NamePointcut.method2() && NamePointcut.method3()”) public void aspectMethod2() { } } 3 織入順序

一個連線點可以同時匹配多個切點,而切點所對應的增強在連線點上織入順序的規則是這樣的:

1.如果在同一個切面類中宣告的增強,則按照增強在切面類中定義的順序進行織入;

org.springframework.core.Ordered org.springframework.core.Ordered 假設有兩個切面類 A 與 B,它們都實現了 Ordered 介面,A 的順序號為 1,B 的順序號為 2,切面類 A 與 B 都定義了 3 個增強,那麼同時匹配這 6 個增強的織入順序如下圖所示:

4 獲取連線點資訊

4.1 JoinPoint

org.aspectj.lang.JoinPoint 介面表示目標類連線點物件,它定義這些主要方法。

方法 說明 Object[] getArgs() 獲取連線點方法執行時的入參列表。 Signature getSignature() 獲取連線點的方法簽名物件。 Object getTarget() 獲取連線點所在的目標物件。 Object getThis() 獲取代理物件。 4.2 ProceedingJoinPoint

org.aspectj.lang.ProceedingJoinPoint 繼承了 JoinPoint 介面,它新增了兩個方法(它們用於執行連線點方法)。

方法 說明 Object proceed() throws Throwable 通過反射執行目標物件連線點處的方法。 Object proceed(Object[] var1) throws Throwable 使用新的入參(替換掉原來的入參),通過反射執行目標物件連線點處的方法。 4.3 示例

Cook 介面:

public interface Cook { /** * 製作食品 / void make(); /* * 製作 * * @param name 食品名稱 */ void make(String name); } CookA 類:

public class CookA implements Cook { public void make() { System.out.println(“製作食品”); } public void make(String name) { System.out.println(“製作” + name); } } 在切面類中訪問連線點資訊:

@Aspectpublic class JoinPointAspect { @Around(“within(net.deniro.spring4.aspectj.CookA)”) public void test(ProceedingJoinPoint pjp) throws Throwable { System.out.println("---------獲取連線點物件【開始】---------"); System.out.println(“引數:” + pjp.getArgs()[0]); System.out.println(“簽名物件:” + pjp.getTarget().getClass()); //執行目標物件方法 pjp.proceed(); System.out.println("---------獲取連線點物件【結束】---------");

}

} Spring bean 配置:

<?xml version="1.0" encoding="UTF-8"?>

輸出結果:

---------獲取連線點物件【開始】---------

引數:壽司

簽名物件:class net.deniro.spring4.aspectj.CookA

製作壽司

---------獲取連線點物件【結束】---------

分享一些知識點給大家希望能幫助到大家,或者從中啟發。

加Q君羊:821169538 都是java愛好者,大家一起討論交流學習

5 繫結連線點的方法入參

args()、this()、target()、@args()、@within()、@target() 和 @annotation() 這些切點函式除可以指定類名外,還可以指定引數名,將目標物件連線點上的方法入參繫結到增強的方法中 。 其中 args() 用於繫結連線點方法的入參, @annotation() 用於繫結連線點方法的註解物件,而 @args() 用於繫結連線點方法入參的註解。

CookC 類:

public class CookC implements Cook { public void make() { System.out.println(“製作食品”); } public void make(String name) { System.out.println(“製作” + name); } public void make(String name, int num) { System.out.println(“製作” + name + " " + num + " 個"); } } 切面類:

@Aspectpublic class ParamsAspect { @Before(“target(net.deniro.spring4.aspectj.CookC) && args(name,num,…)”) public void test(String name,int num) { System.out.println("----------繫結連線點入參【開始】----------"); System.out.println(“name:” + name); System.out.println(“num:” + num); System.out.println("----------繫結連線點入參【結束】----------");

}

} 這裡的連線點表示式 args(name,num,…) 會先找到 name 與 num 的型別,從而生成真正的表示式 args(String,int,…) 。 增強方法可以通過 name 與 num 得到連線點的方法入參。 切點匹配和引數繫結的過程是這樣的:

args() args() 上述示例中的匹配過程如下:

Spring 配置:

注意:這裡必須通過 <aop:aspectj-autoproxy proxy-target-class=“true” /> 來啟用 CGLib 動態代理,這是因為 CookC 的 public void make(String name, int num) 是該類獨有的方法(非介面定義的方法),所以必須使用 CGLib 生成子類的代理方法 。

單元測試:

ApplicationContext context = new ClassPathXmlApplicationContext(“spring-beans.xml”); CookC cookC = (CookC) context.getBean(“cookC”); cookC.make(“壽司”, 100); 輸出結果:

----------繫結連線點入參【開始】----------

name:壽司

num:100

----------繫結連線點入參【結束】----------

製作壽司 100 個

6 繫結代理物件

使用 this() 或 target() 可繫結被代理物件的例項。通過類例項名繫結物件時,依然具有原來連線點匹配的功能,只是類名是由增強方法中的同名入參型別間接決定的。

@Aspectpublic class ProxyAspect {

@Before("this(cook)")
public void bind(Cook cook) {        System.out.println("--------繫結代理物件【開始】--------");        System.out.println(cook.getClass().getName());        System.out.println("--------繫結代理物件【結束】--------");
}

} 首先通過 public void bind(Cook cook) 找出 cook 所對應的型別,接著轉換切點表示式為 this(net.deniro.spring4.aspectj.Cook) 。這樣就表示該切點匹配所有代理物件為 Cook 類中的所有方法。

輸出結果:

--------繫結代理物件【開始】--------

net.deniro.spring4.aspectj.CookCEnhancerBySpringCGLIBEnhancerBySpringCGLIB217fb793

--------繫結代理物件【結束】--------

target() 繫結與 this() 相似。

7 繫結類註解物件

@within() 和 @target() 函式都可以將目標類的註解物件繫結到增強方法中。

定義一個日誌註解類:

@Retention(RetentionPolicy.RUNTIME)//保留期限@Target({ElementType.METHOD,ElementType.TYPE})//目標型別public @interface Log { boolean value() default true;//宣告成員變數} 把該註解類應用於 CookD:

@Logpublic class CookD implements Cook { public void make() { System.out.println(“製作糕點”); } public void make(String name) {

}

} 繫結類註解物件:

@Aspectpublic class ClassAnnotationObjectAspect {

@Before("@within(log)")
public void bind(Log log){        System.out.println("----------繫結類註解物件【開始】----------");        System.out.println(log.getClass().getName());        System.out.println("----------繫結類註解物件【結束】----------");
}

} Spring 配置:

單元測試:

ApplicationContext context = new ClassPathXmlApplicationContext(“spring-beans.xml”); CookD cook = (CookD) context.getBean(“cookD”); cook.make(); 輸出結果:

----------繫結類註解物件【開始】----------

com.sun.proxy.$Proxy8

----------繫結類註解物件【結束】----------

從輸出結果 com.sun.proxy.$Proxy8 可以看出 ,CookD 類的註解 Log 物件也被代理咯O(∩_∩)O哈哈~

8 繫結返回值

在後置增強中,可以通過 returning 來繫結連線點方法的返回值。

切面:

@Aspectpublic class ReturnValueAspect { @AfterReturning(value = “target(net.deniro.spring4.aspectj.CookA)”, returning = “value”) public void bind(boolean value) { System.out.println(“繫結返回值【開始】”); System.out.println(“value:” + value); System.out.println(“繫結返回值【結束】”); } } 注意:returning 的值必須與方法引數名相同。

CookA 新增 smell 方法:

public boolean smell(String name) { System.out.println(name + “香嗎?”); return true; } 單元測試:

ApplicationContext context = new ClassPathXmlApplicationContext(“spring-beans.xml”); CookA cook = (CookA) context.getBean(“cookA”); cook.smell(“烤鴨”); 輸出結果:

烤鴨香嗎?

繫結返回值【開始】

value:true

繫結返回值【結束】

9 繫結異常

可以使用 AfterThrowing 註解的 throwing 成員變數來繫結連線點丟擲的異常。

切面類:

@Aspectpublic class ExceptionAspect {

@AfterThrowing(value = "target(net.deniro.spring4.aspectj.CookA)",throwing = "e")
public void bind(CookException e){        System.out.println("繫結異常【開始】");        System.out.println("e:" + e.getMessage());        System.out.println("繫結異常【結束】");
}

} 注意:throwing 的值必須與方法引數名相同。