76 張圖,剖析 Spring AOP 源碼,小白居然也能看懂,大神,請(qǐng)收下我的膝蓋!

預(yù)計(jì)閱讀 30 分鐘,建議先收藏~~
大家好,我是樓仔!
前兩篇分享的 Spring 源碼,反響非常不錯(cuò),這個(gè)是源碼系列的第 3 篇。
前兩篇的源碼解析,涉及到很多基礎(chǔ)知識(shí),但是源碼的解讀都不難,這篇文章剛好相反,依賴的基礎(chǔ)知識(shí)不多,但是源碼比較難懂。
下面我會(huì)簡(jiǎn)單介紹一下 AOP 的基礎(chǔ)知識(shí),以及使用方法,然后直接對(duì)源碼進(jìn)行拆解。
不 BB,上文章目錄。

1. 基礎(chǔ)知識(shí)
1.1 什么是 AOP ?
AOP 的全稱是 “Aspect Oriented Programming”,即面向切面編程。
在 AOP 的思想里面,周邊功能(比如性能統(tǒng)計(jì),日志,事務(wù)管理等)被定義為切面,核心功能和切面功能分別獨(dú)立進(jìn)行開發(fā),然后把核心功能和切面功能“編織”在一起,這就叫 AOP。
AOP 能夠?qū)⒛切┡c業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯封裝起來,便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度,并有利于未來的可拓展性和可維護(hù)性。
1.2 AOP 基礎(chǔ)概念
連接點(diǎn)(Join point):能夠被攔截的地方,Spring AOP 是基于動(dòng)態(tài)代理的,所以是方法攔截的,每個(gè)成員方法都可以稱之為連接點(diǎn);
切點(diǎn)(Poincut):每個(gè)方法都可以稱之為連接點(diǎn),我們具體定位到某一個(gè)方法就成為切點(diǎn);
增強(qiáng)/通知(Advice):表示添加到切點(diǎn)的一段邏輯代碼,并定位連接點(diǎn)的方位信息,簡(jiǎn)單來說就定義了是干什么的,具體是在哪干;
織入(Weaving):將增強(qiáng)/通知添加到目標(biāo)類的具體連接點(diǎn)上的過程;
引入/引介(Introduction):允許我們向現(xiàn)有的類添加新方法或?qū)傩?,是一種特殊的增強(qiáng);
切面(Aspect):切面由切點(diǎn)和增強(qiáng)/通知組成,它既包括了橫切邏輯的定義、也包括了連接點(diǎn)的定義。
上面的解釋偏官方,下面用“方言”再給大家解釋一遍。
切入點(diǎn)(Pointcut):在哪些類,哪些方法上切入(where);
通知(Advice):在方法執(zhí)行的什么時(shí)機(jī)(when:方法前/方法后/方法前后)做什么(what:增強(qiáng)的功能);
切面(Aspect):切面 = 切入點(diǎn) + 通知,通俗點(diǎn)就是在什么時(shí)機(jī),什么地方,做什么增強(qiáng);
織入(Weaving):把切面加入到對(duì)象,并創(chuàng)建出代理對(duì)象的過程,這個(gè)由 Spring 來完成。
5 種通知的分類:
前置通知(Before Advice):在目標(biāo)方法被調(diào)用前調(diào)用通知功能;
后置通知(After Advice):在目標(biāo)方法被調(diào)用之后調(diào)用通知功能;
返回通知(After-returning):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知功能;
異常通知(After-throwing):在目標(biāo)方法拋出異常之后調(diào)用通知功能;
環(huán)繞通知(Around):把整個(gè)目標(biāo)方法包裹起來,在被調(diào)用前和調(diào)用之后分別調(diào)用通知功能。
1.3 AOP 簡(jiǎn)單示例
新建 Louzai 類:
@Data
@Service
public?class?Louzai?{
????public?void?everyDay()?{
????????System.out.println("睡覺");
????}
}
添加 LouzaiAspect 切面:
applicationContext.xml 添加:
程序入口:
輸出:
吃飯
睡覺
打豆豆。。。
這個(gè)示例非常簡(jiǎn)單,“睡覺” 加了前置和后置通知,但是 Spring 在內(nèi)部是如何工作的呢?
1.4 Spring AOP 工作流程
為了方便大家能更好看懂后面的源碼,我先整體介紹一下源碼的執(zhí)行流程,讓大家有一個(gè)整體的認(rèn)識(shí),否則容易被繞進(jìn)去。
整個(gè) Spring AOP 源碼,其實(shí)分為 3 塊,我們會(huì)結(jié)合上面的示例,給大家進(jìn)行講解。

第一塊就是前置處理,我們?cè)趧?chuàng)建 Louzai Bean 的前置處理中,會(huì)遍歷程序所有的切面信息,然后將切面信息保存在緩存中,比如示例中 LouzaiAspect 的所有切面信息。
第二塊就是后置處理,我們?cè)趧?chuàng)建 Louzai Bean 的后置處理器中,里面會(huì)做兩件事情:
獲取 Louzai 的切面方法:首先會(huì)從緩存中拿到所有的切面信息,和 Louzai 的所有方法進(jìn)行匹配,然后找到 Louzai 所有需要進(jìn)行 AOP 的方法。
創(chuàng)建 AOP 代理對(duì)象:結(jié)合 Louzai 需要進(jìn)行 AOP 的方法,選擇 Cglib 或 JDK,創(chuàng)建 AOP 代理對(duì)象。

第三塊就是執(zhí)行切面,通過“責(zé)任鏈 + 遞歸”,去執(zhí)行切面。
2. 源碼解讀
注意:Spring 的版本是 5.2.15.RELEASE,否則和我的代碼不一樣?。。?/p>
除了原理部分,上面的知識(shí)都不難,下面才是我們的重頭戲,讓你跟著樓仔,走一遍代碼流程。
2.1 代碼入口

這里需要多跑幾次,把前面的 beanName 跳過去,只看 louzai。


進(jìn)入 doGetBean(),進(jìn)入創(chuàng)建 Bean 的邏輯。
2.2 前置處理
主要就是遍歷切面,放入緩存。


這里是重點(diǎn)!敲黑板!?。?/strong>
我們會(huì)先遍歷所有的類;
判斷是否切面,只有切面才會(huì)進(jìn)入后面邏輯;
獲取每個(gè) Aspect 的切面列表;
保存 Aspect 的切面列表到緩存 advisorsCache 中。

到這里,獲取切面信息的流程就結(jié)束了,因?yàn)楹罄m(xù)對(duì)切面數(shù)據(jù)的獲取,都是從緩存 advisorsCache 中拿到。
下面就對(duì)上面的流程,再深入解讀一下。
2.2.1 判斷是否是切面
上圖的第 2 步,邏輯如下:

2.2.2 獲取切面列表



進(jìn)入到 getAdvice(),生成切面信息。

2.3 后置處理

主要就是從緩存拿切面,和 louzai 的方法匹配,并創(chuàng)建 AOP 代理對(duì)象。

進(jìn)入 doCreateBean(),走下面邏輯。


這里是重點(diǎn)!敲黑板!??!
先獲取 louzai 類的所有切面列表;
創(chuàng)建一個(gè) AOP 的代理對(duì)象。

2.3.1 獲取切面
我們先進(jìn)入第一步,看是如何獲取 louzai 的切面列表。
進(jìn)入 buildAspectJAdvisors(),這個(gè)方法應(yīng)該有印象,就是前面將切面信息放入緩存 advisorsCache 中,現(xiàn)在這里就是要獲取緩存。
再回到 findEligibleAdvisors(),從緩存拿到所有的切面信息后,繼續(xù)往后執(zhí)行。
2.3.2 創(chuàng)建代理對(duì)象
有了 louzai 的切面列表,后面就可以開始去創(chuàng)建 AOP 代理對(duì)象。
這里是重點(diǎn)!敲黑板?。?!
這里有 2 種創(chuàng)建 AOP 代理對(duì)象的方式,我們是選用 Cglib 來創(chuàng)建。
我們?cè)倩氐絼?chuàng)建代理對(duì)象的入口,看看創(chuàng)建的代理對(duì)象。
2.4 切面執(zhí)行
通過 “責(zé)任鏈 + 遞歸”,執(zhí)行切面和方法。


前方高能!這塊邏輯非常復(fù)雜?。。?/strong>

下面就是“執(zhí)行切面”最核心的邏輯,簡(jiǎn)單說一下設(shè)計(jì)思路:
設(shè)計(jì)思路:采用遞歸 + 責(zé)任鏈的模式;
遞歸:反復(fù)執(zhí)行 CglibMethodInvocation 的 proceed();
退出遞歸條件:interceptorsAndDynamicMethodMatchers 數(shù)組中的對(duì)象,全部執(zhí)行完畢;
責(zé)任鏈:示例中的責(zé)任鏈,是個(gè)長(zhǎng)度為 3 的數(shù)組,每次取其中一個(gè)數(shù)組對(duì)象,然后去執(zhí)行對(duì)象的 invoke()。
因?yàn)槲覀償?shù)組里面只有 3 個(gè)對(duì)象,所以只會(huì)遞歸 3 次,下面就看這 3 次是如何遞歸,責(zé)任鏈?zhǔn)侨绾螆?zhí)行的,設(shè)計(jì)得很巧妙!
2.4.1 第一次遞歸
數(shù)組的第一個(gè)對(duì)象是 ExposeInvocationInterceptor,執(zhí)行 invoke(),注意入?yún)⑹?CglibMethodInvocation。
里面啥都沒干,繼續(xù)執(zhí)行 CglibMethodInvocation 的 process()。
2.4.2 第二次遞歸
數(shù)組的第二個(gè)對(duì)象是 MethodBeforeAdviceInterceptor,執(zhí)行 invoke()。




2.4.3 第三次遞歸
數(shù)組的第二個(gè)對(duì)象是 AfterReturningAdviceInterceptor,執(zhí)行 invoke()。


執(zhí)行完上面邏輯,就會(huì)退出遞歸,我們看看 invokeJoinpoint() 的執(zhí)行邏輯,其實(shí)就是執(zhí)行主方法。


再回到第三次遞歸的入口,繼續(xù)執(zhí)行后面的切面。
切面執(zhí)行邏輯,前面已經(jīng)演示過,直接看執(zhí)行方法。
后面就依次退出遞歸,整個(gè)流程結(jié)束。
2.4.4 設(shè)計(jì)思路
這塊代碼,我研究了大半天,因?yàn)檫@個(gè)不是純粹的責(zé)任鏈模式。
純粹的責(zé)任鏈模式,對(duì)象內(nèi)部有一個(gè)自身的 next 對(duì)象,執(zhí)行完當(dāng)前對(duì)象的方法末尾,就會(huì)啟動(dòng) next 對(duì)象的執(zhí)行,直到最后一個(gè) next 對(duì)象執(zhí)行完畢,或者中途因?yàn)槟承l件中斷執(zhí)行,責(zé)任鏈才會(huì)退出。
這里 CglibMethodInvocation 對(duì)象內(nèi)部沒有 next 對(duì)象,全程是通過 interceptorsAndDynamicMethodMatchers 長(zhǎng)度為 3 的數(shù)組控制,依次去執(zhí)行數(shù)組中的對(duì)象,直到最后一個(gè)對(duì)象執(zhí)行完畢,責(zé)任鏈才會(huì)退出。
這個(gè)也屬于責(zé)任鏈,只是實(shí)現(xiàn)方式不一樣,后面會(huì)詳細(xì)剖析,下面再討論一下,這些類之間的關(guān)系。
我們的主對(duì)象是 CglibMethodInvocation,繼承于 ReflectiveMethodInvocation,然后 process() 的核心邏輯,其實(shí)都在 ReflectiveMethodInvocation 中。
ReflectiveMethodInvocation 中的 process() 控制整個(gè)責(zé)任鏈的執(zhí)行。
ReflectiveMethodInvocation 中的 process() 方法,里面有個(gè)長(zhǎng)度為 3 的數(shù)組 interceptorsAndDynamicMethodMatchers,里面存儲(chǔ)了 3 個(gè)對(duì)象,分別為 ExposeInvocationInterceptor、MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor。
注意!??!這 3 個(gè)對(duì)象,都是繼承 MethodInterceptor 接口。

然后每次執(zhí)行 invoke() 時(shí),里面都會(huì)去執(zhí)行 CglibMethodInvocation 的 process()。
是不是聽得有些蒙圈?甭著急,我重新再幫你梳理一下。
對(duì)象和方法的關(guān)系:
接口繼承:數(shù)組中的 3 個(gè)對(duì)象,都是繼承 MethodInterceptor 接口,實(shí)現(xiàn)里面的 invoke() 方法;
類繼承:我們的主對(duì)象 CglibMethodInvocation,繼承于 ReflectiveMethodInvocation,復(fù)用它的 process() 方法;
兩者結(jié)合(策略模式):invoke() 的入?yún)ⅲ褪?CglibMethodInvocation,執(zhí)行 invoke() 時(shí),內(nèi)部會(huì)執(zhí)行 CglibMethodInvocation.process(),這個(gè)其實(shí)就是個(gè)策略模式。
可能有同學(xué)會(huì)說,invoke() 的入?yún)⑹?MethodInvocation,沒錯(cuò)!但是 CglibMethodInvocation 也繼承了 MethodInvocation,不信自己可以去看。
執(zhí)行邏輯:
程序入口:是 CglibMethodInvocation 的 process() 方法;
鏈?zhǔn)綀?zhí)行(衍生的責(zé)任鏈模式):process() 中有個(gè)包含 3 個(gè)對(duì)象的數(shù)組,依次去執(zhí)行每個(gè)對(duì)象的 invoke() 方法。
遞歸(邏輯回退):invoke() 方法會(huì)執(zhí)行切面邏輯,同時(shí)也會(huì)執(zhí)行 CglibMethodInvocation 的 process() 方法,讓邏輯再一次進(jìn)入 process()。
遞歸退出:當(dāng)數(shù)字中的 3 個(gè)對(duì)象全部執(zhí)行完畢,流程結(jié)束。
所以這里設(shè)計(jì)巧妙的地方,是因?yàn)榧兇庳?zé)任鏈模式,里面的 next 對(duì)象,需要保證里面的對(duì)象類型完全相同。
但是數(shù)組里面的 3 個(gè)對(duì)象,里面沒有 next 成員對(duì)象,所以不能直接用責(zé)任鏈模式,那怎么辦呢?就單獨(dú)搞了一個(gè) CglibMethodInvocation.process(),通過去無限遞歸 process(),來實(shí)現(xiàn)這個(gè)責(zé)任鏈的邏輯。
這就是我們?yōu)槭裁匆丛创a,學(xué)習(xí)里面優(yōu)秀的設(shè)計(jì)思路!
3. 總結(jié)
我們?cè)傩」?jié)一下,文章先介紹了什么是 AOP,以及 AOP 的原理和示例。
之后再剖析了 AOP 的源碼,分為 3 塊:
將所有的切面都保存在緩存中;
取出緩存中的切面列表,和 louzai 對(duì)象的所有方法匹配,拿到屬于 louzai 的切面列表;
創(chuàng)建 AOP 代理對(duì)象;
通過“責(zé)任鏈 + 遞歸”,去執(zhí)行切面邏輯。
這篇文章,是 Spring 源碼解析的第 3 篇,也是感覺最難的一篇,光圖解代碼就扣了?6 個(gè)小時(shí),整個(gè)人都被扣麻了。
最難的地方還不是摳圖,而是?“切面執(zhí)行”的設(shè)計(jì)思路,雖然流程能走通,但是把整個(gè)設(shè)計(jì)思想能總結(jié)出來,并講得能讓大家明白,還是非常不容易的
