Spring基礎系列--AOP織入邏輯跟蹤
其實在之前的原始碼解讀裡面,關於織入的部分並沒有說清楚,那些前置、後置、環繞、異常等通知是如何圍繞在目標方法周圍執行的呢?
這裡面最重要的就是遞迴,Spring在實現這塊邏輯的時候使用的大量的遞迴呼叫,完美的實現的織入的邏輯。
我們不凡就以Spring基礎系列--AOP實踐中的例子來進行一番邏輯追蹤,來一探究竟。
我們就從測試類開始:
首先我們通過CglibAopProxy類的intercept方法構建一個方法呼叫,並執行其proceed方法:
然後進入ReflectiveMethodInvocation類的proceed()方法,由於currentInterceptorIndex下標從-1開始,在進行++自增操作之後變成0,獲取interceptorsAndDynamicMethodMatchers中的第一個攔截器。
我們看下interceptorsAndDynamicMethodMatchers中的內容:
第一個是ExposeInvocationInterceptor,這個正是之前在Spring基礎系列-AOP原始碼分析中原始碼11裡提到的,獲取通知鏈的時候會將其放到通知鏈首位,用於暴露方法呼叫到ThreadLocal中。
由於其為一個簡單的攔截器,上面的判斷是不成立的,所以直接執行最後一行:
然後進入到ExposeInvocationInterceptor方法的invoke方法中
在這個方法中我們可以看到,90行處,將方法呼叫mi放到了invocation中。那invocation是什麼呢?如下
1 private static final ThreadLocal<MethodInvocation> invocation = new NamedThreadLocal<>("Current AOP method invocation");
可見,是將方法呼叫儲存到了ThreadLocal中,這個invocation儲存的正是當前AOP的方法呼叫。
然後執行mi.proceed()方法,這就是一個遞迴呼叫了。在這裡執行proceed方法,將會繼續執行下一個通知,執行邏輯又回到ReflectiveMethodInvocation類的proceed()方法,這時currentInterceptorIndex自增之後為1,正好指向interceptorsAndDynamicMethodMatchers中的下一個通知:
很顯然,下一個通知為後置異常通知afterThrowingTest。
執行這個通知的invoke方法,程式碼調到AspectJAfterThrowingAdvice類的invoke方法。
在異常通知的invoke方法中直接遞迴呼叫proceed方法,並將剩下所有的呼叫執行全部try...catch住,在最後catch塊中執行其通知邏輯。
除開普通的攔截器之外,最先執行的通知是異常通知,它會將剩下所有的呼叫邏輯全部catch住,也就是說在這期間發生的任何異常都會被捕捉到,而且一旦哪一步發生了異常,那麼執行就會被中斷,到這裡執行catch邏輯。
這下又回到了ReflectiveMethodInvocation類的proceed方法。currentInterceptorIndex再次自增為2,指向通知鏈中第三個通知:
後置返回通知,遵從以上的邏輯我們看看下一步的行為:
在AfterReturningAdviceInterceptor類中
果然去執行後置返回通知的invoke方法去啦,第一步任然是遞迴呼叫,又回到了ReflectiveMethodInvocation類的proceed方法去執行第四個通知:
這個是後置終點通知,這個通知有點特殊哦,我們來看看其invoke方法
它也是先直接進行遞迴呼叫,將其邏輯放在最後,這個是真正的最後,直接放到了finally塊中,這表示什麼,這表示,無論接下來的呼叫操作是否會發生異常,這部分邏輯永遠會執行。
現在又一次回去了ReflectiveMethodInvocation類的proceed方法去執行第五個通知:
嗯,這次是環繞通知,這個通知不同於其他通知,其他通知只執行於方法的一點,這個通知卻需要執行於方法的兩處。所以它會擁有一個ProceedJoinPoint引數,這個引數就是用來區分這兩處執行邏輯的:目標方法之前與之後。
直接執行到最後一句,首次要執行invokeAdviceMethod方法了:
然後執行invokeAdviceMethodWithGivenArgs方法:
它要直接執行通知方法中的邏輯,想想為什麼沒有在進行遞迴呼叫而是直接執行的通知方法呢?
因為環繞通知將遞迴呼叫挪移到了通知邏輯中,由程式設計師自定義執行,來看看我自定義的環繞通知邏輯:
我們先不考慮具體的邏輯,先看看大體結構,顯示一段邏輯,然後執行jp.proceed(os)方法。然後又是一段邏輯。所謂的環繞就是將邏輯圍繞在目標執行前後。
通知的內容真正開始執行了,這裡首先執行了環繞通知的前置部分。
我們執行遞迴呼叫之後,我們發現邏輯跑到了MethodInvocationProceedingJoinPoint中,這是啥類呢?
這竟然是ProceedJoinPoint的實現類,我們執行jp.proceed(os)方法當然會跑到這裡了,
其實ProceedJoinPoint的proceed方法有兩個過載,一個有引數,一個無引數,分別用於針對目標方法有引數和目標發無引數的情況,所以這裡這兩個方法其實邏輯類似,只是有引數的方法會針對引數進行一番操作,將引數設定到方法呼叫中,這麼做,新的方法引數會替換就的方法引數,這也是我們可以在環繞通知中修改引數的原理所在。
第99行就是重設引數的邏輯,方法呼叫中原先其實已經儲存有引數:原有的引數。
第100行程式碼是重點,這裡這裡建立了一個方法呼叫的一個淺拷貝,並使用這個淺拷貝來執行遞迴呼叫proceed方法。
為什麼使用淺拷貝呢?因為我們還希望使用與原來的方法呼叫相同的攔截器鏈和其他物件引用,只不過是需要當前的環繞通知的獨立性罷了(這個可能就是再之前提到的環繞通知會導致其他一些通知功能失效的原因所在了吧)
邏輯又回到ReflectiveMethodInvocation類的proceed方法去執行第六個通知:
這最後一個通知必然就是剩餘的前置通知了。讓我們看看其invoke方法執行。來自:MethodBeforeAdviceInterceptor
這裡執行通知的前置通知邏輯:來自:AspectJMethodBeforeAdvice
呼叫跑到AbstractAspectJAdvice中:
再呼叫:
這裡就是真正呼叫通知邏輯了:來自AspectTest
然後就是一路後退到MethodBeforeAdviceInterceptor類中的invoke方法,繼續執行下一步:
這又是一遞迴呼叫,這個呼叫將會使邏輯再次返回到ReflectiveMethodInvocation類的proceed方法,這一次,由於 所有的通知鏈中的通知都走過一次,剩餘的就是目標方法了。來自:CglibAopProxy
所以這次直接執行目標方法了,哈哈。來自:AspectDemo
反射呼叫目標方法。
執行完目標方法,然後遞迴退回到AspectTest類中的環繞通知aroundTest中去執行環繞通知的後置部分邏輯。
執行完後,繼續遞迴退回,到AspectJAfterAdvice類的invoke方法,去執行finally中的邏輯:
再次執行AbstractAspectJAdvice類的invokeAdviceMethod方法:
呼叫invokeAdviceMethodWithGivenArgs方法
這次執行後置終點通知中的內容:
然後再次遞歸回退到AfterReturningAdviceInterceptor類的invoke方法:
呼叫AspectJAfterReturningAdvice類的afterReturning方法:
這裡不再羅列AbstractAspectJAdvice類中的那兩個方法啦,直接出通知方法:
這次退回到AspectJAfterThrowingAdvice類的invoke方法:
由於整套邏輯未發生異常,所以此處不執行catch塊中的邏輯。然後再次遞歸回退到了ExposeInvocationInterceptor類的invoke方法,來執行finally塊中的邏輯:
這句話將invocation中儲存的方法呼叫置空了。最後回退到DynamicAdvisedInterceptor類的intercept方法繼續下面的邏輯。
好了,到此為止,想說的都說啦,下面就是一些補充:
這裡是為了統一說明,所以講所有的通知全部羅列,在一起呼叫,很明顯返回值的部分就不正確,我想說的是,儘量別把環繞通知和別的通知一起使用,這個通知還是單獨使用比較好。
怎麼樣?如果你全都看完了,那麼有啥感想嗎?
織入的實現就是依靠一個順序通知鏈,再加上遞迴實現的。有沒有感覺這裡的遞迴用的非常的漂亮。層層巢狀,將後執行的部分放到通知鏈前面,在遞迴鏈中以先執行下一條通知的方式層層深入,最裡面是首先執行的通知(前置通知,先把環繞通知撇開),然後再層層退出,執行各自通知的邏輯包括目標方法的邏輯。
之前我有個疑惑就是在ReflectiveMethodInvocation類的proceed方法中,前三行程式碼,順序執行通知鏈,最後執行目標方法,那麼怎麼實現後置的通知呢。看完上面的程式碼追蹤,就瞭解了吧!