@Pointcut 的 12 種用法,你知道幾種?

本文繼續AOP,目前手動Aop中三種方式已經介紹2種了,本文將介紹另外一種:AspectJProxyFactory,可能大家對這個比較陌生,但是@Aspect這個註解大家應該很熟悉吧,通過這個註解在spring環境中實現aop特別的方便。

而AspectJProxyFactory這個類可以通過解析@Aspect標註的類來生成代理aop代理對象,對開發者來說,使創建代理變的更簡潔了。

先了解幾個概念

文中會涉及幾個概念,先了解一下。

target

用來表示目標對象,即需要通過aop來增強的對象。

proxy

代理對象,target通過aop增強之後生成的代理對象。

AspectJ

AspectJ是什麼?

AspectJ是一個面向切面的框架,是目前最好用,最方便的AOP框架,和spring中的aop可以集成在一起使用,通過Aspectj提供的一些功能實現aop代理變得非常方便。

AspectJ使用步驟

1.創建一個類,使用@Aspect標註
2.@Aspect標註的類中,通過@Pointcut定義切入點
3.@Aspect標註的類中,通過AspectJ提供的一些通知相關的註解定義通知
4.使用AspectJProxyFactory結合@Ascpect標註的類,來生成代理對象
先來個案例,感受一下AspectJ是多麼的方便。

來個類

package com.javacode2018.aop.demo9.test1;

public class Service1 {

    public void m1() {
        System.out.println("我是 m1 方法");
    }

    public void m2() {
        System.out.println(10 / 0);
        System.out.println("我是 m2 方法");
    }
}

通過AspectJ來對Service1進行增強,來2個通知,一個前置通知,一個異常通知,這2個通知需要對Service1中的所有方法生效,實現如下:

package com.javacode2018.aop.demo9.test1;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

//@1:這個類需要使用@Aspect進行標註
@Aspect
public class Aspect1 {

    //@2:定義了一個切入點,可以匹配Service1中所有方法
    @Pointcut("execution(* com.javacode2018.aop.demo9.test1.Service1.*(..))")
    public void pointcut1() {
    }

    //@3:定義了一個前置通知,這個通知對剛剛上面我們定義的切入點中的所有方法有效
    @Before(value = "pointcut1()")
    public void before(JoinPoint joinPoint) {
        //輸出連接點的信息
        System.out.println("前置通知," + joinPoint);
    }

    //@4:定義了一個異常通知,這個通知對剛剛上面我們定義的切入點中的所有方法有效
    @AfterThrowing(value = "pointcut1()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        //發生異常之後輸出異常信息
        System.out.println(joinPoint + ",發生異常:" + e.getMessage());
    }

}

@1:類上使用@Aspect標註
@2:通過@Pointcut註解標註在方法上面,用來定義切入點
@3:使用@Before標註在方法上面,定義了一個前置通知,通過value引用了上面已經定義的切入點,表示這個通知會對Service1中的所有方法生效,在通知中可以通過這個類名.方法名()引用@Pointcut定義的切入點,表示這個通知對這些切入點有效,若@Before和@Pointcut在一個類的時候,直接通過方法名()引用當前類中定義的切入點
@4:這個使用@AfterThrowing定義了一個異常通知,也是對通過value引用了上面已經定義的切入點,表示這個通知會對Service1中的所有方法生效,若Service1中的方法拋出了Exception類型的異常,都會回調afterThrowing方法。
來個測試類

package com.javacode2018.aop.demo9;

import com.javacode2018.aop.demo9.test1.Aspect1;
import com.javacode2018.aop.demo9.test1.Service1;
import org.junit.Test;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;

public class AopTest9 {
    @Test
    public void test1() {
        try {
            //對應目標對象
            Service1 target = new Service1();
            //創建AspectJProxyFactory對象
            AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
            //設置被代理的目標對象
            proxyFactory.setTarget(target);
            //設置標註了@Aspect註解的類
            proxyFactory.addAspect(Aspect1.class);
            //生成代理對象
            Service1 proxy = proxyFactory.getProxy();
            //使用代理對象
            proxy.m1();
            proxy.m2();
        } catch (Exception e) {
        }
    }
}

運行輸出

前置通知,execution(void com.javacode2018.aop.demo9.test1.Service1.m1())
我是 m1 方法
前置通知,execution(void com.javacode2018.aop.demo9.test1.Service1.m2())
execution(void com.javacode2018.aop.demo9.test1.Service1.m2()),發生異常:/ by zero

使用是不是特方便。

AspectJProxyFactory原理

@Aspect標註的類上,這個類中,可以通過通過@Pointcut來定義切入點,可以通過@Before、@Around、@After、@AfterRunning、@AfterThrowing標註在方法上來定義通知,定義好了之後,將@Aspect標註的這個類交給AspectJProxyFactory來解析生成Advisor鏈,進而結合目標對象一起來生成代理對象,大家可以去看一下源碼,比較簡單,這裏就不多解釋了。

本文的重點在@Aspect標註的類上,@Aspect中有2個關鍵點比較重要

@Pointcut:標註在方法上,用來定義切入點,有11種用法,本文主要講解這11種用法。
@Aspect類中定義通知:可以通過@Before、@Around、@After、@AfterRunning、@AfterThrowing標註在方法上來定義通知,這個下一篇介紹。

@Pointcut的12種用法

作用

用來標註在方法上來定義切入點。

定義

格式:@ 註解(value=“表達標籤 (表達式格式)”)

如:

@Pointcut(“execution(* com.javacode2018.aop.demo9.test1.Service1.*(…))”)

表達式標籤(10種)

  • execution:用於匹配方法執行的連接點
  • within:用於匹配指定類型內的方法執行
  • this:用於匹配當前AOP代理對象類型的執行方法;注意是AOP代理對象的類型匹配,這樣就可能包括引入接口也* 類型匹配
  • target:用於匹配當前目標對象類型的執行方法;注意是目標對象的類型匹配,這樣就不包括引入接口也類型匹配
  • args:用於匹配當前執行的方法傳入的參數爲指定類型的執行方法
  • @within:用於匹配所以持有指定註解類型內的方法
  • @target:用於匹配當前目標對象類型的執行方法,其中目標對象持有指定的註解
  • @args:用於匹配當前執行的方法傳入的參數持有指定註解的執行
  • @annotation:用於匹配當前執行方法持有指定註解的方法
  • bean:Spring AOP擴展的,AspectJ沒有對於指示符,用於匹配特定名稱的Bean對象的執行方法
    10種標籤組成了12種用法

1、execution

使用execution(方法表達式)匹配方法執行。

execution格式

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
  • 其中帶 ?號的 modifiers-pattern?,declaring-type-pattern?,hrows-pattern?是可選項
  • ret-type-pattern,name-pattern, parameters-pattern是必選項
  • modifier-pattern? 修飾符匹配,如public 表示匹配公有方法
  • ret-type-pattern 返回值匹配,* 表示任何返回值,全路徑的類名等
  • declaring-type-pattern? 類路徑匹配
  • name-pattern 方法名匹配,* 代表所有,set*,代表以set開頭的所有方法
  • (param-pattern) 參數匹配,指定方法參數(聲明的類型),(…)代表所有參數,(,String)代表第一個參數爲任何值,第 * 二個爲String類型,(…,String)代表最後一個參數是String類型
  • throws-pattern? 異常類型匹配

舉例說明

在這裏插入圖片描述

類型匹配語法

很多地方會按照類型的匹配,先來說一下類型匹配的語法。

首先讓我們來了解下AspectJ類型匹配的通配符:

*:匹配任何數量字符
…:匹配任何數量字符的重複,如在類型模式中匹配任何數量子包;而在方法參數模式中匹配任何數量參數(0個或者多個參數)
+:匹配指定類型及其子類型;僅能作爲後綴放在類型模式後邊

2、within

用法

within(類型表達式):目標對象target的類型是否和within中指定的類型匹配

匹配原則

target.getClass().equals(within表達式中指定的類型)

案例

有2個類,父子關係

父類C1

package com.javacode2018.aop.demo9.test2;

public class C1 {
    public void m1() {
        System.out.println("我是m1");
    }

    public void m2() {
        System.out.println("我是m2");
    }
}

子類C2

package com.javacode2018.aop.demo9.test2;

public class C2 extends C1 {
    @Override
    public void m2() {
        super.m2();
    }

    public void m3() {
        System.out.println("我是m3");
    }
}

來個Aspect類

package com.javacode2018.aop.demo9.test2;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest2 {

    @Pointcut("within(C1)") //@1
    public void pc() {
    }

    @Before("pc()") //@2
    public void beforeAdvice(JoinPoint joinpoint) {
        System.out.println(joinpoint);
    }

}

注意@1匹配的類型是C1,也就是說被代理的對象的類型必須是C1類型的纔行,需要和C1完全匹配
下面我們對C2創建代理

@Test
public void test2(){
    C2 target = new C2();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest2.class);

    C2 proxy = proxyFactory.getProxy();
    proxy.m1();
    proxy.m2();
    proxy.m3();
}

運行輸出

我是m1
我是m2
我是m3

原因是目標對象是C2類型的,C2雖然是C1的子類,但是within中表達式指定的是要求類型必須是C1類型的才匹配。

如果將within表達式修改爲下面任意一種就可以匹配了

@Pointcut("within(C1+)") 
@Pointcut("within(C2)") 
```
再次運行輸出
```
execution(void com.javacode2018.aop.demo9.test2.C1.m1())
我是m1
execution(void com.javacode2018.aop.demo9.test2.C2.m2())
我是m2
execution(void com.javacode2018.aop.demo9.test2.C2.m3())
我是m3
```
### 3、this

### 用法

this(類型全限定名):通過aop創建的代理對象的類型是否和this中指定的類型匹配;注意判斷的目標是代理對象;this中使用的表達式必須是類型全限定名,不支持通配符。

### 匹配原則
```
如:this(x),則代理對象proxy滿足下麪條件時會匹配
x.getClass().isAssignableFrom(proxy.getClass());

案例

來個接口

package com.javacode2018.aop.demo9.test3;

public interface I1 {
    void m1();
}

來個實現類

package com.javacode2018.aop.demo9.test3;

public class Service3 implements I1 {

    @Override
    public void m1() {
        System.out.println("我是m1");
    }

}

來個@Aspect類

package com.javacode2018.aop.demo9.test3;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest3 {

    //@1:匹配proxy是Service3類型的所有方法
    @Pointcut("this(Service3)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinpoint) {
        System.out.println(joinpoint);
    }

}

測試代碼

@Test
public void test3() {
    Service3 target = new Service3();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    //獲取目標對象上的接口列表
    Class<?>[] allInterfaces = ClassUtils.getAllInterfaces(target);
    //設置需要代理的接口
    proxyFactory.setInterfaces(allInterfaces);
    proxyFactory.addAspect(AspectTest3.class);
    //獲取代理對象
    Object proxy = proxyFactory.getProxy();
    //調用代理對象的方法
    ((I1) proxy).m1();

    System.out.println("proxy是否是jdk動態代理對象:" + AopUtils.isJdkDynamicProxy(proxy));
    System.out.println("proxy是否是cglib代理對象:" + AopUtils.isCglibProxy(proxy));
    //判斷代理對象是否是Service3類型的
    System.out.println(Service3.class.isAssignableFrom(proxy.getClass()));
}

運行輸出

我是m1
proxy是否是jdk動態代理對象:true
proxy是否是cglib代理對象:false
false

從輸出中可以看出m1方法沒有被增強,原因:this表達式要求代理對象必須是Service3類型的,輸出中可以看出代理對象並不是Service3類型的,此處代理對象proxy是使用jdk動態代理生成的。

我們可以將代碼調整一下,使用cglib來創建代理

proxyFactory.setProxyTargetClass(true);

再次運行,會發現m2被攔截了,結果如下

execution(void com.javacode2018.aop.demo9.test3.Service3.m1())
我是m1
proxy是否是jdk動態代理對象:false
proxy是否是cglib代理對象:true
true

4、target

用法

target(類型全限定名):判斷目標對象的類型是否和指定的類型匹配;注意判斷的是目標對象的類型;表達式必須是類型全限定名,不支持通配符。

匹配原則

如:target(x),則目標對象target滿足下麪條件時會匹配
x.getClass().isAssignableFrom(target.getClass());
案例

package com.javacode2018.aop.demo9.test4;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest4 {

    //@1:目標類型必須是Service3類型的
    @Pointcut("target(com.javacode2018.aop.demo9.test3.Service3)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinpoint) {
        System.out.println(joinpoint);
    }

}

測試代碼

@Test
public void test4() {
    Service3 target = new Service3();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setProxyTargetClass(true);
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest4.class);
    //獲取代理對象
    Object proxy = proxyFactory.getProxy();
    //調用代理對象的方法
    ((I1) proxy).m1();
    //判斷target對象是否是Service3類型的
    System.out.println(Service3.class.isAssignableFrom(target.getClass()));
}

運行輸出

execution(void com.javacode2018.aop.demo9.test3.Service3.m1())
我是m1
true
within、this、target對比

在這裏插入圖片描述

5、args

用法

args(參數類型列表)匹配當前執行的方法傳入的參數是否爲args中指定的類型;注意是匹配傳入的參數類型,不是匹配方法簽名的參數類型;參數類型列表中的參數必須是類型全限定名,不支持通配符;args屬於動態切入點,也就是執行方法的時候進行判斷的,這種切入點開銷非常大,非特殊情況最好不要使用。

舉例說明

案例

下面的m1方法參數是Object類型的。

package com.javacode2018.aop.demo9.test5;

public class Service5 {
    public void m1(Object object) {
        System.out.println("我是m1方法,參數:" + object);
    }
}
Aspect類

package com.javacode2018.aop.demo9.test5;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.util.Arrays;
import java.util.stream.Collectors;

@Aspect
public class AspectTest5 {
    //@1:匹配只有1個參數其類型是String類型的
    @Pointcut("args(String)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinpoint) {
        System.out.println("請求參數:" + Arrays.stream(joinpoint.getArgs()).collect(Collectors.toList()));
    }
}
測試代碼,調用2次m1方法,第一次傳入一個String類型的,第二次傳入一個int類型的,看看效果

@Test
public void test5() {
    Service5 target = new Service5();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest5.class);
    Service5 proxy = proxyFactory.getProxy();
    proxy.m1("路人");
    proxy.m1(100);
}

運行輸出

請求參數:[路人]
我是m1方法,參數:路人
我是m1方法,參數:100

輸出中可以看出,m1第一次調用被增強了,第二次沒有被增強。

args會在調用的過程中對參數實際的類型進行匹配,比較耗時,慎用。

6、@within

用法

@within(註解類型):匹配指定的註解內定義的方法。

匹配規則

調用目標方法的時候,通過java中Method.getDeclaringClass()獲取當前的方法是哪個類中定義的,然後會看這個類上是否有指定的註解。

被調用的目標方法Method對象.getDeclaringClass().getAnnotation(within中指定的註解類型) != null
來看3個案例。

案例1

目標對象上有@within中指定的註解,這種情況時,目標對象的所有方法都會被攔截。

來個註解

package com.javacode2018.aop.demo9.test9;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Ann9 {
}

來個目標類,用@Ann9標註

package com.javacode2018.aop.demo9.test9;

@Ann9
public class S9 {
    public void m1() {
        System.out.println("我是m1方法");
    }
}

來個Aspect類

package com.javacode2018.aop.demo9.test9;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest9 {
    /**
     * 定義目標方法的類上有Ann9註解
     */
    @Pointcut("@within(Ann9)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println(joinPoint);
    }
}

測試代碼

@Test
public void test9() {
    S9 target = new S9();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest9.class);
    S9 proxy = proxyFactory.getProxy();
    proxy.m1();
}

m1方法在類S9中定義的,S9上面有Ann9註解,所以匹配成功
運行輸出

execution(void com.javacode2018.aop.demo9.test9.S9.m1())
我是m1方法

案例2

定義註解時未使用@Inherited,說明子類無法繼承父類上的註解,這個案例中我們將定義一個這樣的註解,將註解放在目標類的父類上,來看一下效果。

定義註解Ann10

package com.javacode2018.aop.demo9.test10;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Ann10 {
}

來2個父子類

注意:

S10Parent爲父類,並且使用了Anno10註解,內部定義了2個方法大家注意一下
而S10位代理的目標類,繼承了S10Parent,內部重寫了父類的m2方法,並且又新增了一個m3方法
package com.javacode2018.aop.demo9.test10;

@Ann10
class S10Parent {

    public void m1() {
        System.out.println("我是S10Parent.m1()方法");
    }

    public void m2() {
        System.out.println("我是S10Parent.m2()方法");
    }
}

public class S10 extends S10Parent {

    @Override
    public void m2() {
        System.out.println("我是S10.m2()方法");
    }

    public void m3() {
        System.out.println("我是S10.m3()方法");
    }
}

來個Aspect類

package com.javacode2018.aop.demo9.test10;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest10 {
    //匹配目標方法聲明的類上有@Anno10註解
    @Pointcut("@within(com.javacode2018.aop.demo9.test10.Ann10)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println(joinPoint);
    }
}

測試用例

S10爲目標類,依次執行代理對象的m1、m2、m3方法,最終會調用目標類target中對應的方法。
@Test
public void test10() {
    S10 target = new S10();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest10.class);
    S10 proxy = proxyFactory.getProxy();
    proxy.m1();
    proxy.m2();
    proxy.m3();
}

運行輸出

execution(void com.javacode2018.aop.demo9.test10.S10Parent.m1())
我是S10Parent.m1()方法
我是S10.m2()方法
我是S10.m3()方法

分析結果

從輸出中可以看出,只有m1方法被攔截了,其他2個方法沒有被攔截。

確實是這樣的,m1方法的是由S10Parent定義的,這個類上面有Ann10註解。

而m2方法雖然也在S10Parent中定義了,但是這個方法被子類S10重寫了,所以調用目標對象中的m2方法的時候,此時發現m2方法是由S10定義的,而S10.class.getAnnotation(Ann10.class)爲空,所以這個方法不會被攔截。

同樣m3方法也是S10中定義的,也不會被攔截。

案例3

對案例2進行改造,在註解的定義上面加上@Inherited,此時子類可以繼承父類的註解,此時3個方法都會被攔截了。

下面上代碼,下面代碼爲案例2代碼的一個拷貝,不同地方只是註解的定義上多了@Inherited

定義註解Ann11

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Ann11 {
}

2個父子類

package com.javacode2018.aop.demo9.test11;

@Ann11
class S11Parent {

    public void m1() {
        System.out.println("我是S11Parent.m1()方法");
    }

    public void m2() {
        System.out.println("我是S11Parent.m2()方法");
    }
}

public class S11 extends S11Parent {

    @Override
    public void m2() {
        System.out.println("我是S11.m2()方法");
    }

    public void m3() {
        System.out.println("我是S11.m3()方法");
    }
}

Aspect類

package com.javacode2018.aop.demo9.test11;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest11 {

    @Pointcut("@within(com.javacode2018.aop.demo9.test11.Ann11)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println(joinPoint);
    }
}

測試用例

@Test
public void test11() {
    S11 target = new S11();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest11.class);
    S11 proxy = proxyFactory.getProxy();
    proxy.m1();
    proxy.m2();
    proxy.m3();
}

運行輸出

execution(void com.javacode2018.aop.demo9.test11.S11Parent.m1())
我是S11Parent.m1()方法
execution(void com.javacode2018.aop.demo9.test11.S11.m2())
我是S11.m2()方法
execution(void com.javacode2018.aop.demo9.test11.S11.m3())
我是S11.m3()方法
這次3個方法都被攔截了。

7、@target

用法

@target(註解類型):判斷目標對象target類型上是否有指定的註解;@target中註解類型也必須是全限定類型名。

匹配規則

target.class.getAnnotation(指定的註解類型) != null
2種情況可以匹配

註解直接標註在目標類上
註解標註在父類上,但是註解必須是可以繼承的,即定義註解的時候,需要使用@Inherited標註
案例1

註解直接標註在目標類上,這種情況目標類會被匹配到。

自定義一個註解Ann6

package com.javacode2018.aop.demo9.test6;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Ann6 {
}

目標類S6上直接使用@Ann1

package com.javacode2018.aop.demo9.test6;

@Ann6
public class S6 {
    public void m1() {
        System.out.println("我是m1");
    }
}

來個Aspect

package com.javacode2018.aop.demo9.test6;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest6 {
    //@1:目標類上有@Ann1註解
    @Pointcut("@target(Ann1)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println(joinPoint);
    }
}

測試代碼

@Test
public void test6() {
    S6 target = new S6();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest6.class);
    S6 proxy = proxyFactory.getProxy();
    proxy.m1();
    System.out.println("目標類上是否有 @Ann6 註解:" + (target.getClass().getAnnotation(Ann6.class) != null));
}

運行輸出

execution(void com.javacode2018.aop.demo9.test6.S6.m1())
我是m1
目標類上是否有 @Ann6 註解:true

案例2

註解標註在父類上,註解上沒有@Inherited,這種情況下,目標類無法匹配到,下面看代碼

註解Ann7

package com.javacode2018.aop.demo9.test7;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Ann7 {
}

來2個父子類,父類上有@Ann7,之類S7爲目標類

package com.javacode2018.aop.demo9.test7;

import java.lang.annotation.Target;

@Ann7
class S7Parent {
}

public class S7 extends S7Parent {
    public void m1() {
        System.out.println("我是m1");
    }

    public static void main(String[] args) {
        System.out.println(S7.class.getAnnotation(Target.class));
    }
}

來個Aspect類

package com.javacode2018.aop.demo9.test7;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest7 {
    /**
     * 匹配目標類上有Ann7註解
     */
    @Pointcut("@target(com.javacode2018.aop.demo9.test7.Ann7)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println(joinPoint);
    }
}

測試代碼

@Test
public void test7() {
    S7 target = new S7();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest7.class);
    S7 proxy = proxyFactory.getProxy();
    proxy.m1();
    System.out.println("目標類上是否有 @Ann7 註解:" + (target.getClass().getAnnotation(Ann7.class) != null));
}

運行輸出

我是m1
目標類上是否有 @Ann7 註解:false

分析結果

@Ann7標註在了父類上,但是@Ann7定義的時候沒有使用@Inherited,說明之類無法繼承父類上面的註解,所以上面的目標類沒有被攔截,下面我們將@Ann7的定義改一下,加上@Inherited

package com.javacode2018.aop.demo9.test7;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Ann7 {
}

再次運行輸出

execution(void com.javacode2018.aop.demo9.test7.S7.m1())
我是m1
目標類上是否有 @Ann7 註解:true

此時目標對象被攔截了。

8、@args

用法

@args(註解類型):方法參數所屬的類上有指定的註解;注意不是參數上有指定的註解,而是參數類型的類上有指定的註解。

案例1

@Pointcut("@args(Ann8)"):匹配方法只有一個參數,並且參數所屬的類上有Ann8註解
可以匹配下面的代碼,m1方法的第一個參數類型是Car類型,Car類型上有註解Ann8

@Ann8
class Car {
}

public void m1(Car car) {
    System.out.println("我是m1");
}

案例2

@Pointcut("@args(*,Ann8)"):匹配方法只有2個參數,且第2個參數所屬的類型上有Ann8註解
可以匹配下面代碼

@Ann8
class Car {
}

public void m1(String name,Car car) {
    System.out.println("我是m1");
}

案例3

@Pointcut("@args(..,com.javacode2018.aop.demo9.test8.Ann8)"):匹配參數數量大於等於1,且最後一個參數所屬的類型上有Ann8註解
@Pointcut("@args(*,com.javacode2018.aop.demo9.test8.Ann8,..)"):匹配參數數量大於等於2,且第2個參數所屬的類型上有Ann8註解
@Pointcut("@args(..,com.javacode2018.aop.demo9.test8.Ann8,*)"):匹配參數數量大於等於2,且倒數第2個參數所屬的類型上有Ann8註解

這個案例代碼,大家自己寫一下,體驗一下。

9、@annotation

用法

@annotation(註解類型):匹配被調用的方法上有指定的註解。

案例

定義一個註解,可以用在方法上

package com.javacode2018.aop.demo9.test12;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ann12 {
}

定義2個類

S12Parent爲父類,內部定義了2個方法,2個方法上都有@Ann12註解
S12是代理的目標類,也是S12Parent的子類,內部重寫了m2方法,重寫之後m2方法上並沒有@Ann12註解,S12內部還定義2個方法m3和m4,而m3上面有註解@Ann12
package com.javacode2018.aop.demo9.test12;

class S12Parent {

    @Ann12
    public void m1() {
        System.out.println("我是S12Parent.m1()方法");
    }

    @Ann12
    public void m2() {
        System.out.println("我是S12Parent.m2()方法");
    }
}

public class S12 extends S12Parent {

    @Override
    public void m2() {
        System.out.println("我是S12.m2()方法");
    }

    @Ann12
    public void m3() {
        System.out.println("我是S12.m3()方法");
    }

    public void m4() {
        System.out.println("我是S12.m4()方法");
    }
}

來個Aspect類

當被調用的目標方法上有@Ann12註解的時,會被beforeAdvice處理。
package com.javacode2018.aop.demo9.test12;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest12 {

    @Pointcut("@annotation(com.javacode2018.aop.demo9.test12.Ann12)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println(joinPoint);
    }
}

測試用例

S12作爲目標對象,創建代理,然後分別調用4個方法
@Test
public void test12() {
    S12 target = new S12();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest12.class);
    S12 proxy = proxyFactory.getProxy();
    proxy.m1();
    proxy.m2();
    proxy.m3();
    proxy.m4();
}

運行輸出

execution(void com.javacode2018.aop.demo9.test12.S12Parent.m1())
我是S12Parent.m1()方法
我是S12.m2()方法
execution(void com.javacode2018.aop.demo9.test12.S12.m3())
我是S12.m3()方法
我是S12.m4()方法

分析結果

m1方法位於S12Parent中,上面有@Ann12註解,被連接了,m3方法上有@Ann12註解,被攔截了,而m4上沒有@Ann12註解,沒有被攔截,這3個方法的執行結果都很容易理解。

重點在於m2方法的執行結果,沒有被攔截,m2方法雖然在S12Parent中定義的時候也有@Ann12註解標註,但是這個方法被S1給重寫了,在S1中定義的時候並沒有@Ann12註解,代碼中實際上調用的是S1中的m2方法,發現這個方法上並沒有@Ann12註解,所以沒有被攔截。

10、bean

用法

bean(bean名稱):這個用在spring環境中,匹配容器中指定名稱的bean。

案例

來個類BeanService

package com.javacode2018.aop.demo9.test13;

public class BeanService {
    private String beanName;

    public BeanService(String beanName) {
        this.beanName = beanName;
    }

    public void m1() {
        System.out.println(this.beanName);
    }
}

來個Aspect類

package com.javacode2018.aop.demo9.test13;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
public class Aspect13 {
    //攔截spring容器中名稱爲beanService2的bean
    @Pointcut("bean(beanService2)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println(joinPoint);
    }
}

來個spring配置類

package com.javacode2018.aop.demo9.test13;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy // 這個可以啓用通過AspectJ方式自動爲符合條件的bean創建代理
public class MainConfig13 {

    //將Aspect13註冊到spring容器
    @Bean
    public Aspect13 aspect13() {
        return new Aspect13();
    }

    @Bean
    public BeanService beanService1() {
        return new BeanService("beanService1");
    }

    @Bean
    public BeanService beanService2() {
        return new BeanService("beanService2");
    }
}

這個配置類中有個@EnableAspectJAutoProxy,這個註解大家可能比較陌生,這個屬於aop中自動代理的範圍,後面會有文章詳細介紹這塊,這裏大家暫時先不用關注。
測試用例

下面啓動spring容器,加載配置類MainConfig13,然後分別獲取beanService1和beanService2,調用他們的m1方法,看看效果

@Test
public void test13() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig13.class);
    //從容器中獲取beanService1
    BeanService beanService1 = context.getBean("beanService1", BeanService.class);
    beanService1.m1();
    //從容器中獲取beanService2
    BeanService beanService2 = context.getBean("beanService2", BeanService.class);
    beanService2.m1();
}

運行輸出

beanService1
execution(void com.javacode2018.aop.demo9.test13.BeanService.m1())
beanService2
beanService2的m1方法被攔截了。

11、reference pointcut

表示引用其他命名切入點。

有時,我們可以將切入專門放在一個類中集中定義。

其他地方可以通過引用的方式引入其他類中定義的切入點。

語法如下:

@Pointcut(“完整包名類名.方法名稱()”)
若引用同一個類中定義切入點,包名和類名可以省略,直接通過方法就可以引用。
比如下面,我們可以將所有切入點定義在一個類中

package com.javacode2018.aop.demo9.test14;

import org.aspectj.lang.annotation.Pointcut;

public class AspectPcDefine {
    @Pointcut("bean(bean1)")
    public void pc1() {
    }

    @Pointcut("bean(bean2)")
    public void pc2() {
    }
}

下面頂一個一個Aspect類,來引用上面的切入點

package com.javacode2018.aop.demo9.test14;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Aspect14 {

    @Pointcut("com.javacode2018.aop.demo9.test14.AspectPcDefine.pc1()")
    public void pointcut1() {
    }

    @Pointcut("com.javacode2018.aop.demo9.test14.AspectPcDefine.pc1() || com.javacode2018.aop.demo9.test14.AspectPcDefine.pc2()")
    public void pointcut2() {
    }

}

12、組合型的pointcut

Pointcut定義時,還可以使用&&、||、!運算符。

&&:多個匹配都需要滿足
||:多個匹配中只需滿足一個
!:匹配不滿足的情況下

@Pointcut("bean(bean1) || bean(bean2)") //匹配bean1或者bean2
@Pointcut("@target(Ann1) && @Annotation(Ann2)") //匹配目標類上有Ann1註解並且目標方法上有Ann2註解
@Pointcut("@target(Ann1) && !@target(Ann2)") // 匹配目標類上有Ann1註解但是沒有Ann2註解

總結

本文詳解了@Pointcut的12種用法,案例大家一定要敲一遍,敲的過程中,會遇到問題,然後解決問題,才能夠加深理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章