Spring Boot AOP @Pointcut攔截註解的表達式與運算符

項目場景:

這裏主要說下Spring Boot AOP中@Pointcut攔截類上面的註解與方法上面的註解,怎麼寫表達式怎麼,還有@Pointcut中使用運算符。


@PointCut 表達式

攔截註解的表達式有3種:@annotation、@within、@target


1、@annotation

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

@annotation(com.test.aop.demo.MyAnnotation)

2、@within

匹配包含某個註解的類(註解作用在類上面)

@within(com.test.aop.demo.MyAnnotation)

3、@target

匹配目標對象有指定註解的類(註解作用在類上面)

@target(com.test.aop.demo.MyAnnotation)

@target 和@within的區別:
        1、@target(註解A):判斷被調用的目標對象中是否聲明瞭註解A,如果有,會被攔截;

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

        3、@target關注的是被調用的對象,@within關注的是調用的方法所在的類;


@PointCut中的運算符

 PointCut中可以使用&&、||、! 運算符

同時匹配方法上的和類上的註解 

 
  1. @Pointcut("@annotation(com.test.aop.demo.MyAnnotation) || @within(com.test.aop.demo.MyAnnotation)")
  2. public void cutController(){
  3. }
 

 或者

 
  1. @Pointcut("@annotation(com.test.aop.demo.MyAnnotation)")
  2. public void cutController(){
  3. }
  4.  
  5. @Pointcut("@within(com.test.aop.demo.MyAnnotation)")
  6. public void cutService(){
  7. }
  8.  
  9. @Pointcut("cutController() || cutService()")
  10. public void cutAll(){}
     
     

    導言

    什麼是PCD

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

    PCD一覽圖

    image-20210108140219721

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

    execution

    execution是最常用的PCD。它的匹配式模板如下展示:

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

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

    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表達式

 

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