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

1 切點複合運算

支持在切點定義中加入如下運算符進行復合運算:java

運算符 說明
&& 與運算。
! 非運算。
|| 或運算。

2 切點命名

通常狀況下,切點是直接聲明在須要加強方法處,這種切點的聲明方式稱爲匿名切點,匿名切點只能在聲明處被使用 。  若是但願在其它地方能夠重用這個切點,咱們能夠經過 @Pointcut 註解及切面類方法來命名它。spring

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 織入順序

一個鏈接點能夠同時匹配多個切點,而切點所對應的加強在鏈接點上織入順序的規則是這樣的:this

1.若是在同一個切面類中聲明的加強,則按照加強在切面類中定義的順序進行織入;spa

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"?><beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

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

    <bean id="cookA" class="net.deniro.spring4.aspectj.CookA"/>
    <bean class="net.deniro.spring4.aspectj.JoinPointAspect"/></beans>

輸出結果:

---------獲取鏈接點對象【開始】---------

參數:壽司

簽名對象: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 配置:

<!--aspectj 驅動器 --><aop:aspectj-autoproxy proxy-target-class="true"/><bean id="cookC" class="net.deniro.spring4.aspectj.CookC"/><bean class="net.deniro.spring4.aspectj.ParamsAspect"/>

注意:這裏必須經過 <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.CookC$$EnhancerBySpringCGLIB$$217fb793 

--------綁定代理對象【結束】--------

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 配置:

<!--aspectj 驅動器 --><aop:aspectj-autoproxy proxy-target-class="true"/><bean id="cookD" class="net.deniro.spring4.aspectj.CookD"/><bean class="net.deniro.spring4.aspectj.ClassAnnotationObjectAspect"/>

單元測試:

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 的值必須與方法參數名相同。

單元測試:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
CookA cook = (CookA) context.getBean("cookA");
cook.make("");

輸出結果:

綁定異常【開始】

e:煮啥呢???

綁定異常【結束】

相關文章
相關標籤/搜索