您好,登錄后才能下訂單哦!
這篇文章主要介紹了Spring AOP對象內部方法間如何嵌套調用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
之前面試官問了一個問題,大概意思就是一個類有兩個成員方法 A 和 B,兩者都加了事務處理注解,定義了事務傳播級別為 REQUIRE_NEW,問 A 方法內部直接調用 B 方法時能否觸發事務處理機制。
答案有點復雜,Spring 的事務處理其實是通過AOP實現的,而實現AOP的方法有好幾種,對于通過 Jdk 和 cglib 實現的 aop 處理,上述問題的答案為否,對于通過AspectJ實現的,上述問題答案為是。
本文就結合具體例子來看一下
public interface AopActionInf { void doSomething_01(); void doSomething_02(); }
public class AopActionImpl implements AopActionInf{ public void doSomething_01() { System.out.println("AopActionImpl.doSomething_01()"); //內部調用方法 doSomething_02 this.doSomething_02(); } public void doSomething_02() { System.out.println("AopActionImpl.doSomething_02()"); } }
public class ActionAspectXML { public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("進入環繞通知"); Object object = pjp.proceed();//執行該方法 System.out.println("退出方法"); return object; } } <aop:aspectj-autoproxy/> <bean id="actionImpl" class="com.maowei.learning.aop.AopActionImpl"/> <bean id="actionAspectXML" class="com.maowei.learning.aop.ActionAspectXML"/> <aop:config> <aop:aspect id = "aspectXML" ref="actionAspectXML"> <aop:pointcut id="anyMethod" expression="execution(* com.maowei.learning.aop.AopActionImpl.*(..))"/> <aop:around method="aroundMethod" pointcut-ref="anyMethod"/> </aop:aspect> </aop:config>
運行結果如下:
下圖是斷點分析在調用方法doSomething_02時的線程棧,很明顯在調用doSomething_02時并沒有對其進行AOP處理。
默認情況下,Spring AOP使用Jdk的動態代理機制實現,當然也可以通過如下配置更改為cglib實現,但是運行結果相同,此處不再贅述。
<aop:aspectj-autoproxy proxy-target-class="true"/>
那有沒有辦法能夠觸發AOP處理呢?答案是有的,考慮到AOP是通過動態生成目標對象的代理對象而實現的,那么只要在調用方法時改為調用代理對象的目標方法即可。
我們將調用 doSomething_02 的那行代碼改成如下,并修改相應配置信息:
public void doSomething_01() { System.out.println("AopActionImpl.doSomething_01()"); ((AopActionInf) AopContext.currentProxy()).doSomething_02(); } <aop:aspectj-autoproxy expose-proxy="true"/>
先來看一下運行結果,
從運行結果可以看出,嵌套調用方法已經能夠實現AOP處理了,同樣我們看一下線程調用棧信息,顯然 doSomething_02 方法被增強處理了(紅框中內容)。
首先定義一個目標對象:
/** * @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對象內部方法間如何嵌套調用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。