最全 SpringAOP切面表達式

往期相關文章:html

AOP核心概念和SpringAOP切面java

10分鐘入門SpringAOPspring

導言

什麼是PCD

PCD(pointcut designators )就是SpringAOP的切點表達式。SpringAOP的PCD是徹底兼容AspectJ的,一共有10種。ide

PCD一覽圖

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6e849cf4c0e4d8bb527db4aaddc65e0~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210108140219721" style="zoom:50%;" />優化

使用指南

SpringAOP是基於動態代理實現的,如下以目標對象表示被代理bean,代理對象表示AOP構建出來的bean。目標方法表示被代理的方法。this

execution

execution是最經常使用的PCD。它的匹配式模板以下展現:idea

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
          throws-pattern?)
execution(修飾符匹配式? 返回類型匹配式 類名匹配式? 方法名匹配式(參數匹配式) 異常匹配式?)

代碼塊中帶?符號的匹配式都是可選的,對於execution PCD必不可少的只有三個:.net

  1. 返回值匹配值
  2. 方法名匹配式
  3. 參數匹配式

舉例分析: execution(public * ServiceDemo.*(..)) 匹配public修飾符,返回值是*,即任意返回值類型都行,ServiceDemo是類名匹配式~不必定要全路徑,只要全局依可見性惟一就行~,.*是方法名匹配式,匹配全部方法,..是參數匹配式,匹配任意數量、任意類型參數的方法。代理

再舉一些其餘例子:調試

  • execution(* com.xyz.service..*.*(..)): 匹配com.xyz.service及其子包下的任意方法。
  • execution(* joke(Object+))):匹配任意名字爲joke的方法,且其動態入參是是Object類型或該類的子類。
  • execution(* joke(String,..)):匹配任意名字爲joke的方法,該方法 一個入參爲String(不能夠爲子類),後面能夠有任意個入參且入參類型不限
  • execution(* com..*.*Dao.find*(..)): 匹配指定包下find開頭的方法
  • execution(* com.baobaotao.Waiter+.*(..)) : 匹配com.baobaotao包下Waiter及其子類的全部方法。

within

篩選出某包下的全部類,注意要帶有*

  • within(com.xyz.service.*)com.xyz.service包下的類,不包括子包
  • within(com.xyz.service..*)com.xyz.service包下及其子包下的類

this

經常使用於命名綁定模式。對由代理對象的類型進行過濾篩選。

若是目標類是基於接口實現的,則this()中能夠填該接口的全路徑名,不然~非接口實現~因爲是基於CGLIB實現的,this中能夠填寫目標類的全路徑名。

this(com.xyz.service.AccountService): 代理類是com.xyz.service.AccountService或其子類。

使用@EnableAspectJAutoProxy(proxyTargetClass = true)能夠強制使用CGLIB。不然默認首先使用jdk動態代理,jdk代理不了纔會用CGLIB。

target

this做用於代理對象,target做用於目標對象。

target(com.xyz.service.AccountService): 被代理類(目標對象)是com.xyz.service.AccountService或其子類

args

經常使用於對目標方法的參數匹配。通常不單獨使用,而是配合其餘PCD來使用。args能夠使用命名綁定模式,以下舉例:

@Aspect // 切面聲明
@Component // 注入IOC
@Slf4j
class AspectDemo {
    @Around("within(per.aop.*) && args(str)") // 在per.aop包下,且被代理方法的只有一個參數,參數類型是String或者其子類
    @SneakyThrows
    public Object logAspect(ProceedingJoinPoint pjp, String str) { 
        String signature = pjp.getSignature().toString();
        log.info("{} start,param={}", signature, pjp.getArgs());
        Object res = pjp.proceed();
        log.info("{} end", signature);
        return res;
    }
}
  1. 若是args中是參數名,則配合切面(advice)方法的使用來肯定要匹配的方法參數類型。
  2. 若是args中是類型,例如@Around("within(per.aop.*) && args(String)」),則能夠沒必要使用切面方法來肯定類型,但此時也不能使用參數綁定了~見下文~了。

雖然args()支持+符號,但本省args()就支持子類通配。

和帶參數匹配execution區別

舉個例子: args(com.xgj.Waiter)等價於 execution(* *(com.xgj.Waiter+))。並且execution不能支持帶參數的advice。

@target

使用場景舉例: 當一個Service有多個子類時, 某些子類須要打日誌,某些子類不須要打日誌時能夠以下處理(配合java多態):

篩選出具備給定註解的被代理對象~是對象不是類,@target是動態的~。以下自定義一個註解LogAble:

//全限定名: annotation.LogAble
@Target({ElementType.TYPE,ElementType.PARAMETER}) // 支持在方法參數、類上注
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAble {
}

假如須要「註上了這個註解的全部類的的public方法「都打日誌的話~日誌邏輯要自定義~,能夠以下這麼寫PCD,固然對應方法的bean要注入到SpringIOC容器中:

@Around("@target(annotation.LogAble) && execution(public * *.*(..))")
// 自定義日誌邏輯

@args

對於目標方法參數的運行時類型要有@args指定的註解。是方法參數的類型上有指定註解,不是方法參數上帶註解。

使用場景: 假如參數類型有多個子類,只有某個子類才能夠匹配該PCD。

  • @args(com.ms.aop.jargs.demo1.Anno1): 匹配1個參數,且第1個參數運行時須要有Anno1註解
  • @args(com.ms.aop.jargs.demo1.Anno1,..)匹配一個或多個參數,第一個參數運行時須要帶上Anno1註解。
  • @args(com.ms.aop.jargs.demo1.Anno1,com.ms.aop.jargs.demo1.Anno2): 一參匹配Anno1,二參匹配Annno2 。

@within

運行時類型的@target。@target關注的是被調用的對象,@within關注的是調用的方法所在的類。

@target 和 @within 的不一樣點:

@target(註解A):判斷被調用的目標對象中是否聲明瞭註解A,若是有,會被攔截

@within(註解A): 判斷被調用的方法所屬的中是否聲明瞭註解A,若是有,會被攔截

@annotation

匹配有指定註解的方法(註解做用在方法上面)

bean

根據beanNam來匹配。支持*通配符。

bean(*Service) // 匹配全部Service結尾的Service

其餘

組合使用

PCD之間支持,&& || !三種運算符。上文示例中就使用了&& 運算符。||表示或(不是短路或)。!表示非。

命名綁定模式

上文中的 @Around("within(per.aop.*) && args(str)")示例就是使用了命名綁定模式,在PCD中寫上變量名,在方法上對變量名的類型進行限定。

@Around("within(per.aop.*) && args(str)")
public Object logAspect(ProceedingJoinPoint pjp, String str) { ...}

如上舉例,str要是String類型或其子類,且方法入參只能有一個。

name binding only allowed in target, this, and args pcds

命名綁定模式只支持target、this、args三種PCD。

argNames

觀察源碼能夠發現,全部的Advice註解都帶有argNames字段,例如@Around:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Around {
    String value();
    String argNames() default "";
}

什麼狀況下會使用這個屬性呢,以下舉例解釋:

@Around(value = "execution(* TestBean.paramArgs(..))  && args(decimal,str,..)&& target(bean)", argNames = "pjp,str,decimal,bean")
@SneakyThrows // proceed會拋受檢異常
Object aroundArgs(ProceedingJoinPoint pjp,/*使用命名綁定模式*/ String str, BigDecimal decimal, Object bean) {
    // 在方法執行前作一些操做
    return  pjp.proceed();
}

argnames 必需要和args、target、this標籤一塊兒使用。雖然實際操做中能夠不帶,但官方建議全部帶參數的都帶,緣由以下:

所以若是‘ argernames’屬性沒有指定,那麼 Spring AOP 將查看類的調試信息,並嘗試從局部變量表中肯定參數名。只要使用調試信息(至少是‘-g: vars’)編譯了類,就會出現此信息。使用這個標誌編譯的結果是:

(1)你的代碼將會更容易被反向工程)

(2)類文件大小將會很是大(一般是可有可無的)

(3)刪除未使用的局部變量的優化將不會被編譯器應用。

此外,若是編譯的代碼沒有必要的調試信息,那麼 Spring AOP 將嘗試推斷綁定變量與參數的配對。若是變量的綁定在可用信息下是不明確的,那麼一個 AmbiguousBindingException 就會被拋出。若是上面的策略都失敗了,那麼就會拋出一個 IllegalArgumentException。

建議全部的advice註解裏都帶 argNames,反正idea也會提醒。

參考文章

  1. Spring文檔
  2. SpringAOP Point表達式
相關文章
相關標籤/搜索