往期相關文章:html
10分鐘入門SpringAOPspring
PCD(pointcut designators )就是SpringAOP的切點表達式。SpringAOP的PCD是徹底兼容AspectJ的,一共有10種。ide
<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是最經常使用的PCD。它的匹配式模板以下展現:idea
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) execution(修飾符匹配式? 返回類型匹配式 類名匹配式? 方法名匹配式(參數匹配式) 異常匹配式?)
代碼塊中帶?
符號的匹配式都是可選的,對於execution PCD
必不可少的只有三個:.net
舉例分析: 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(com.xyz.service.*)
com.xyz.service包下的類,不包括子包within(com.xyz.service..*)
com.xyz.service包下及其子包下的類經常使用於命名綁定模式
。對由代理對象的類型進行過濾篩選。
若是目標類是基於接口實現的,則this()
中能夠填該接口
的全路徑名,不然~非接口實現~因爲是基於CGLIB實現的,this中能夠填寫目標類
的全路徑名。
this(com.xyz.service.AccountService)
: 代理類是com.xyz.service.AccountService或其子類。
使用@EnableAspectJAutoProxy(proxyTargetClass = true)
能夠強制使用CGLIB。不然默認首先使用jdk動態代理,jdk代理不了纔會用CGLIB。
this做用於代理對象,target做用於目標對象。
target(com.xyz.service.AccountService)
: 被代理類(目標對象)是com.xyz.service.AccountService或其子類
經常使用於對目標方法的參數匹配。通常不單獨使用,而是配合其餘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; } }
參數名
,則配合切面(advice)方法的使用來肯定要匹配的方法參數類型。@Around("within(per.aop.*) && args(String)」)
,則能夠沒必要使用切面方法來肯定類型,但此時也不能使用參數綁定了~見下文~了。雖然args()
支持+
符號,但本省args()
就支持子類通配。
execution
區別舉個例子: args(com.xgj.Waiter)
等價於 execution(* *(com.xgj.Waiter+))
。並且execution不能支持帶參數的advice。
使用場景舉例: 當一個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
指定的註解。是方法參數的類型上有指定註解,不是方法參數上帶註解。
使用場景: 假如參數類型有多個子類,只有某個子類才能夠匹配該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 。非運行時類型的
的@target
。@target關注的是被調用的對象,@within關注的是調用的方法所在的類。
@target 和 @within 的不一樣點:
@target(註解A):判斷被調用的目標對象
中是否聲明瞭註解A,若是有,會被攔截
@within(註解A): 判斷被調用的方法所屬的類
中是否聲明瞭註解A,若是有,會被攔截
匹配有指定註解的方法(註解做用在方法上面)
根據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。
觀察源碼能夠發現,全部的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也會提醒。