往期相關文章:
AOP核心概念和SpringAOP切面
10分鐘入門SpringAOP
文章目錄
- 導言
- 什么是PCD
- PCD一覽圖
- 使用指南
- execution
- within
- this
- target
- args
- 和帶引數匹配`execution`區別
- @target
- @args
- @within
- @annotation
- bean
- 其他
- 組合使用
- 命名系結模式
- argNames
- 參考文章
導言
什么是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也會提醒,
參考文章
- Spring檔案
- SpringAOP Point運算式
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/246640.html
標籤:java
