您好,登錄后才能下訂單哦!
這篇文章主要介紹了Spring AOP對象內(nèi)部方法間如何嵌套調(diào)用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
之前面試官問了一個問題,大概意思就是一個類有兩個成員方法 A 和 B,兩者都加了事務處理注解,定義了事務傳播級別為 REQUIRE_NEW,問 A 方法內(nèi)部直接調(diào)用 B 方法時能否觸發(fā)事務處理機制。
答案有點復雜,Spring 的事務處理其實是通過AOP實現(xiàn)的,而實現(xiàn)AOP的方法有好幾種,對于通過 Jdk 和 cglib 實現(xiàn)的 aop 處理,上述問題的答案為否,對于通過AspectJ實現(xiàn)的,上述問題答案為是。
本文就結(jié)合具體例子來看一下
public interface AopActionInf { void doSomething_01(); void doSomething_02(); }
public class AopActionImpl implements AopActionInf{ public void doSomething_01() { System.out.println("AopActionImpl.doSomething_01()"); //內(nèi)部調(diào)用方法 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("進入環(huán)繞通知"); Object object = pjp.proceed();//執(zhí)行該方法 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>
運行結(jié)果如下:
下圖是斷點分析在調(diào)用方法doSomething_02時的線程棧,很明顯在調(diào)用doSomething_02時并沒有對其進行AOP處理。
默認情況下,Spring AOP使用Jdk的動態(tài)代理機制實現(xiàn),當然也可以通過如下配置更改為cglib實現(xiàn),但是運行結(jié)果相同,此處不再贅述。
<aop:aspectj-autoproxy proxy-target-class="true"/>
那有沒有辦法能夠觸發(fā)AOP處理呢?答案是有的,考慮到AOP是通過動態(tài)生成目標對象的代理對象而實現(xiàn)的,那么只要在調(diào)用方法時改為調(diào)用代理對象的目標方法即可。
我們將調(diào)用 doSomething_02 的那行代碼改成如下,并修改相應配置信息:
public void doSomething_01() { System.out.println("AopActionImpl.doSomething_01()"); ((AopActionInf) AopContext.currentProxy()).doSomething_02(); } <aop:aspectj-autoproxy expose-proxy="true"/>
先來看一下運行結(jié)果,
從運行結(jié)果可以看出,嵌套調(diào)用方法已經(jīng)能夠?qū)崿F(xiàn)AOP處理了,同樣我們看一下線程調(diào)用棧信息,顯然 doSomething_02 方法被增強處理了(紅框中內(nèi)容)。
首先定義一個目標對象:
/** * @description: 目標對象與方法 * @create: 2020-12-20 17:10 */ public class TargetClassDefinition { public void method1(){ method2(); System.out.println("method1 執(zhí)行了……"); } public void method2(){ System.out.println("method2 執(zhí)行了……"); } }
在這個類定義中,method1()方法會調(diào)用同一對象上的method2()方法。
現(xiàn)在,我們使用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()的執(zhí)行。
接下來將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(); } }
執(zhí)行之后,得到如下結(jié)果:
method2 執(zhí)行了……
method1 執(zhí)行了……
PT in method [method1]>>>>>>StopWatch '': running time = 20855400 ns; [] took 20855400 ns = 100%
-------------------
method2 執(zhí)行了……
PT in method [method2]>>>>>>StopWatch '': running time = 71200 ns; [] took 71200 ns = 100%
不難發(fā)現(xiàn),從外部直接調(diào)用TargetClassDefinition的method2()方法的時候,因為該方法簽名匹配AspectDefinition中的Around Advice所對應的Pointcut定義,所以Around Advice邏輯得以執(zhí)行,也就是說AspectDefinition攔截method2()成功了。但是,當調(diào)用method1()時,只有method1()方法執(zhí)行攔截成功,而method1()方法內(nèi)部的method2()方法沒有執(zhí)行卻沒有被攔截。
這種結(jié)果的出現(xiàn),歸根結(jié)底是Spring AOP的實現(xiàn)機制造成的。眾所周知Spring AOP使用代理模式實現(xiàn)AOP,具體的橫切邏輯會被添加到動態(tài)生成的代理對象中,只要調(diào)用的是目標對象的代理對象上的方法,通常就可以保證目標對象上的方法執(zhí)行可以被攔截。就像TargetClassDefinition的method2()方法執(zhí)行一樣。
不過,代理模式的實現(xiàn)機制在處理方法調(diào)用的時序方面,會給使用這種機制實現(xiàn)的AOP產(chǎn)品造成一個遺憾,一般的代理對象方法與目標對象方法的調(diào)用時序如下所示:
proxy.method2(){ 記錄方法調(diào)用開始時間; target.method2(); 記錄方法調(diào)用結(jié)束時間; 計算消耗的時間并記錄到日志; }
在代理對象方法中,無論如何添加橫切邏輯,不管添加多少橫切邏輯,最終還是需要調(diào)用目標對象上的同一方法來執(zhí)行最初所定義的方法邏輯。
如果目標對象中原始方法調(diào)用依賴于其他對象,我們可以為目標對象注入所需依賴對象的代理,并且可以保證想用的JoinPoint被攔截并織入橫切邏輯。而一旦目標對象中的原始方法直接調(diào)用自身方法的時候,也就是說依賴于自身定義的其他方法時,就會出現(xiàn)如下圖所示問題:
在代理對象的method1()方法執(zhí)行經(jīng)歷了層層攔截器后,最終會將調(diào)用轉(zhuǎn)向目標對象上的method1(),之后的調(diào)用流程全部都是在TargetClassDefinition中,當method1()調(diào)用method2()時,它調(diào)用的是TargetObject上的method2()而不是ProxyObject上的method2()。而針對method2()的橫切邏輯,只織入到了ProxyObject上的method2()方法中。所以,在method1()中調(diào)用的method2()沒有能夠被攔截成功。
當目標對象依賴于其他對象時,我們可以通過為目標對象注入依賴對象的代理對象,來解決相應的攔截問題。
當目標對象依賴于自身時,我們可以嘗試將目標對象的代理對象公開給它,只要讓目標對象調(diào)用自身代理對象上的相應方法,就可以解決內(nèi)部調(diào)用的方法沒有被攔截的問題。
Spring AOP提供了AopContext來公開當前目標對象的代理對象,我們只要在目標對象中使用AopContext.currentProxy()就可以取得當前目標對象所對應的代理對象。重構(gòu)目標對象,如下所示:
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 執(zhí)行了……"); } public void method2(){ System.out.println("method2 執(zhí)行了……"); } }
要使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" />
再次執(zhí)行代碼,即可實現(xiàn)所需效果:
method2 執(zhí)行了……
PT in method [method2]>>>>>>StopWatch '': running time = 180400 ns; [] took 180400 ns = 100%
method1 執(zhí)行了……
PT in method [method1]>>>>>>StopWatch '': running time = 24027700 ns; [] took 24027700 ns = 100%
-------------------
method2 執(zhí)行了……
PT in method [method2]>>>>>>StopWatch '': running time = 64200 ns; [] took 64200 ns = 100%
感謝你能夠認真閱讀完這篇文章,希望小編分享的“Spring AOP對象內(nèi)部方法間如何嵌套調(diào)用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學習!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。