同一物件內的嵌套方法呼叫AOP失效分析
舉一個同一物件內的嵌套方法呼叫攔截失效的例子
首先定義一個目標物件:
/**
* @description: 目標物件與方法
* @create: 2020-12-20 17:10
*/
public class TargetClassDefinition {
public void method1(){
method2();
System.out.println("method1 執行了……");
}
public void method2(){
System.out.println("method2 執行了……");
}
}
在這個類定義中,method1()方法會呼叫同一物件上的method2()方法,
現在,我們使用Spring AOP攔截該類定義的method1()和method2()方法,比如一個簡單的性能檢測邏輯,定義如下Aspect:
/**
* @description: 性能檢測Aspect定義
* @create: 2020-12-20 17:13
*/
@Aspect
public class AspectDefinition {
@Pointcut("execution(public void *.method1())")
public void method1(){}
@Pointcut("execution(public void *.method2())")
public void method2(){}
@Pointcut("method1() || method2()")
public void pointcutCombine(){}
@Around("pointcutCombine()")
public Object aroundAdviceDef(ProceedingJoinPoint pjp) throws Throwable{
StopWatch stopWatch = new StopWatch();
try{
stopWatch.start();
return pjp.proceed();
}finally {
stopWatch.stop();
System.out.println("PT in method [" + pjp.getSignature().getName() + "]>>>>>>"+stopWatch.toString());
}
}
}
由AspectDefinition定義可知,我們的Around Advice會攔截pointcutCombine()所指定的JoinPoint,即method1()或method2()的執行,
接下來將AspectDefinition中定義的橫切邏輯織入TargetClassDefinition并運行,其代碼如下:
/**
* @description: 啟動方法
* @create: 2020-12-20 17:23
*/
public class StartUpDefinition {
public static void main(String[] args) {
AspectJProxyFactory weaver = new AspectJProxyFactory(new TargetClassDefinition());
weaver.setProxyTargetClass(true);
weaver.addAspect(AspectDefinition.class);
Object proxy = weaver.getProxy();
((TargetClassDefinition) proxy).method1();
System.out.println("-------------------");
((TargetClassDefinition) proxy).method2();
}
}
執行之后,得到如下結果:
method2 執行了……
method1 執行了……
PT in method [method1]>>>>>>StopWatch '': running time = 20855400 ns; [] took 20855400 ns = 100%
-------------------
method2 執行了……
PT in method [method2]>>>>>>StopWatch '': running time = 71200 ns; [] took 71200 ns = 100%
不難發現,從外部直接呼叫TargetClassDefinition的method2()方法的時候,因為該方法簽名匹配AspectDefinition中的Around Advice所對應的Pointcut定義,所以Around Advice邏輯得以執行,也就是說AspectDefinition攔截method2()成功了,但是,當呼叫method1()時,只有method1()方法執行攔截成功,而method1()方法內部的method2()方法沒有執行卻沒有被攔截,
原因分析
這種結果的出現,歸根結底是Spring AOP的實作機制造成的,眾所周知Spring AOP使用代理模式實作AOP,具體的橫切邏輯會被添加到動態生成的代理物件中,只要呼叫的是目標物件的代理物件上的方法,通常就可以保證目標物件上的方法執行可以被攔截,就像TargetClassDefinition的method2()方法執行一樣,
不過,代理模式的實作機制在處理方法呼叫的時序方面,會給使用這種機制實作的AOP產品造成一個遺憾,一般的代理物件方法與目標物件方法的呼叫時序如下所示:
proxy.method2(){
記錄方法呼叫開始時間;
target.method2();
記錄方法呼叫結束時間;
計算消耗的時間并記錄到日志;
}
在代理物件方法中,無論如何添加橫切邏輯,不管添加多少橫切邏輯,最侄訓是需要呼叫目標物件上的同一方法來執行最初所定義的方法邏輯,
如果目標物件中原始方法呼叫依賴于其他物件,我們可以為目標物件注入所需依賴物件的代理,并且可以保證想用的JoinPoint被攔截并織入橫切邏輯,而一旦目標物件中的原始方法直接呼叫自身方法的時候,也就是說依賴于自身定義的其他方法時,就會出現如下圖所示問題:

在代理物件的method1()方法執行經歷了層層攔截器后,最侄訓將呼叫轉向目標物件上的method1(),之后的呼叫流程全部都是在TargetClassDefinition中,當method1()呼叫method2()時,它呼叫的是TargetObject上的method2()而不是ProxyObject上的method2(),而針對method2()的橫切邏輯,只織入到了ProxyObject上的method2()方法中,所以,在method1()中呼叫的method2()沒有能夠被攔截成功,
解決方案
當目標物件依賴于其他物件時,我們可以通過為目標物件注入依賴物件的代理物件,來解決相應的攔截問題,
當目標物件依賴于自身時,我們可以嘗試將目標物件的代理物件公開給它,只要讓目標物件呼叫自身代理物件上的相應方法,就可以解決內部呼叫的方法沒有被攔截的問題,
Spring AOP提供了AopContext來公開當前目標物件的代理物件,我們只要在目標物件中使用AopContext.currentProxy()就可以取得當前目標物件所對應的代理物件,重構目標物件,如下所示:
import org.springframework.aop.framework.AopContext;
/**
* @description: 目標物件與方法
* @create: 2020-12-20 17:10
*/
public class TargetClassDefinition {
public void method1(){
((TargetClassDefinition) AopContext.currentProxy()).method2();
// method2();
System.out.println("method1 執行了……");
}
public void method2(){
System.out.println("method2 執行了……");
}
}
要使AopContext.currentProxy()生效,需要在生成目標物件的代理物件時,將ProxyConfig或者它相應的子類的exposeProxy屬性設定為true,如下所示:
/**
* @description: 啟動方法
* @create: 2020-12-20 17:23
*/
public class StartUpDefinition {
public static void main(String[] args) {
AspectJProxyFactory weaver = new AspectJProxyFactory(new TargetClassDefinition());
weaver.setProxyTargetClass(true);
weaver.setExposeProxy(true);
weaver.addAspect(AspectDefinition.class);
Object proxy = weaver.getProxy();
((TargetClassDefinition) proxy).method1();
System.out.println("-------------------");
((TargetClassDefinition) proxy).method2();
}
}
<!-- 在XML檔案中的開啟方式 -->
<aop:aspectj-autoproxy expose-proxy="true" />
再次執行代碼,即可實作所需效果:
method2 執行了……
PT in method [method2]>>>>>>StopWatch '': running time = 180400 ns; [] took 180400 ns = 100%
method1 執行了……
PT in method [method1]>>>>>>StopWatch '': running time = 24027700 ns; [] took 24027700 ns = 100%
-------------------
method2 執行了……
PT in method [method2]>>>>>>StopWatch '': running time = 64200 ns; [] took 64200 ns = 100%
后記
雖然通過將目標物件的代理物件賦給目標物件實作了我們的目的,但解決的方式不夠雅觀,我們的目標物件都直接系結到了Spring AOP的具體API上了,因此,在開發中應該盡量避免“自呼叫”的情況,
摘自 《Spring 揭秘》 王福強 人民郵電出版社
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/237891.html
標籤:其他
