SpringAOP 和 AspectJ 的關系:
它們是兩種不同的編程風格, SpringAOP 使用 xml 配置的形式配置 aop。而 AspectJ 使用 AspectJ 的注解來配置 aop
aspect、JoinPoint、Pointcut、Weaving、Advice
JoinPoint: 連接點。表示目標對象中的方法
Pointcut: 切點。表示連接點的集合
Weaving: 織入。把代理邏輯加入到目標對象上的過程叫織入
Advice: 通知。包括 “around”, “before” and “after 等
this、target
this: 產生的代理對象
target: 被代理的原始對象
JoinPoint、ProceedingJoinPoint
JoinPoint 接口可以拿到連接點的相關信息,比如:方法簽名、方法參數、this、target
ProceedingJoinPoint 繼承自 JoinPoint,它是用來支持環繞(around)通知的,多暴露了一個 proceed() 方法,用來執行目標對象的方法。
除了 ret-type-pattern, name-pattern, param-pattern 以外,其他的部分都是可選的(? 表示可選項)。
* 最常用作返回類型格式(ret-type-pattern),它表示匹配任何返回類型。 * 也可以用作名稱格式(name-pattern)的全部或一部分。 參數格式(param-pattern)稍微復雜一些: () : 匹配不帶參數的方法 (..) : 匹配任意數量(零個或多個)的參數 (*) : 匹配任何類型的一個參數的方法 (*,String) : 匹配兩個參數的方法,第一個可以是任何類型,而第二個必須是字符串
更多語法可以參考 AspectJ 的文檔:https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html
execution 與 within 的區別
execution 與 within 的區別:粒度不一樣 execution 的最小粒度可以控制到方法級別; within 的最小粒度只能控制到類級別;
例子:
execution(public * *(..)) // 匹配任意公共方法
execution(* set*(..)) // 匹配任意名稱為 set 開頭的方法
execution(* com.xyz.service.AccountService.*(..)) // 匹配 AccountService 類中的任意方法
execution(* com.xyz.service.*.*(..)) // 匹配 com.xyz.service 包下的任意類的任意方法
execution(* com.xyz.service..*.*(..)) // 匹配 com.xyz.service 包及子包下的任意類的任意方法
within(com.xyz.service.*) // 匹配 com.xyz.service 包下的任意類的任意方法
within(com.xyz.service..*) // 匹配 com.xyz.service 包及子包下的任意類的任意方法
this 和 target 的區別
this 匹配的是代理對象,即從 SpringIOC 容器中拿出來的代理對象;
target 匹配的是目標對象,即被代理的原始對象。
this(com.xyz.service.AccountService) // 匹配代理對象的所有方法,且代理對象實現了 AccountService
target(com.xyz.service.AccountService) // 匹配目標對象,且目標對象實現了 AccountService
注意: 使用 this 和 target 時,要注意代理實現是 JDK 還是 Cglib,兩者的表現不同。
-
使用 JDK 動態代理產生的代理類(this)是繼承了 java.lang.reflect.Proxy 后再實現了被代理的接口的,產生的代理類的代碼里面會去調用目標類(target),再結合 InvocationHandler 來產生代理效果。
-
使用 Cglib 動態代理產生的代理類(this)是直接繼承了被代理的目標類的(target)。
例子:
public interface AccountService {...}
public class AccountServiceImpl {...}
開啟 AOP 的方式:
-
@PointCut("this(com.xyz.service.AccountServiceImpl)") : 它表示匹配代理對象的所有方法,且代理對象是 AccountServiceImpl 的實例,即 "代理對象 instanceof AccountServiceImpl == true"。 如果 AccountService 是代理 bean 的話,也會是 JDK 動態代理產生的代理 bean,那么它的 proxyClass 是一個繼承了 java.lang.reflect.Proxy 並且實現了 AccountService 接口的一個代理類,類似於 public class $proxy extend Proxy implements AccountService {},所以,這個代理類不是 AccountServiceImpl 的實例。故這個切面不起作用。
-
@PointCut("target(com.xyz.service.AccountServiceImpl)") : 它表示匹配目標對象的所有方法,且目標對象是 AccountServiceImpl 的實例。顯然,在 Test 類中目標對象就是 AccountServiceImpl,所以切面會起作用。
-
將代理改為默認使用 Cglib : @EnableAspectJAutoProxy(proxyTargetClass=true) 由於 Cglib 動態代理是采用繼承目標類的方式來實現的,所以,@PointCut("this(com.xyz.service.AccountServiceImpl)") 與 @PointCut("target(com.xyz.service.AccountServiceImpl)") 兩個切面都會生效。
args
匹配指定參數類型和參數數據的方法。
args(java.io.Serializable) // 匹配只有一個參數的方法,且參數類型為 Serializable
通過這個表達式還可以為切面通知傳參,具體參考:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-ataspectj-advice-params-generics
@target、@within、@annotation、@args
這些都是與注解一起使用的。
注意:@annotation 只能用來匹配方法,而 @target、@within 可以用來匹配類。(粒度不同)
bean
bean(tradeService) // 匹配 bean 的名字叫 tradeService 的類
bean(*Service) // 匹配 bean 的名字以 Service 結尾的
通過 && 、||、! 來組合
上面所有的表達式都可以通過 && 、||、! 來組合使用。
處理泛型&給 Advice 傳參
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}