項目場景:
這裏主要說下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中可以使用&&、||、! 運算符
同時匹配方法上的和類上的註解
-
-
public void cutController(){
-
}
或者
-
-
public void cutController(){
-
}
-
-
-
public void cutService(){
-
}
-
-
-
public void cutAll(){}
導言
什麼是PCD
PCD(pointcut designators )就是SpringAOP的切點表達式。SpringAOP的PCD是完全兼容AspectJ的,一共有10種。
PCD一覽圖
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
必不可少的只有三個:- 返回值匹配值
- 方法名匹配式
- 參數匹配式
舉例分析:
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; } }
- 如果args中是
參數名
,則配合切面(advice)方法的使用來確定要匹配的方法參數類型。 - 如果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也會提醒。參考文章