76 張圖,剖析 Spring AOP

大家好,我是蘇三!

前兩篇分享的 Spring 源碼,反響非常不錯,這個是源碼系列的第 3 篇

前兩篇的源碼解析,涉及到很多基礎知識,但是源碼的解讀都不難,這篇文章剛好相反,依賴的基礎知識不多,但是源碼比較難懂。

下面我會簡單介紹一下 AOP 的基礎知識,以及使用方法,然後直接對源碼進行拆解。

不 BB,上文章目錄。

  1. 基礎知識 =======

1.1 什麼是 AOP ?

AOP 的全稱是 “Aspect Oriented Programming”,即面向切面編程

在 AOP 的思想裏面,周邊功能(比如性能統計,日誌,事務管理等)被定義爲切面,核心功能和切面功能分別獨立進行開發,然後把核心功能和切面功能 “編織” 在一起,這就叫 AOP。

AOP 能夠將那些與業務無關,卻爲業務模塊所共同調用的邏輯封裝起來,便於減少系統的重複代碼,降低模塊間的耦合度,並有利於未來的可拓展性和可維護性。

1.2 AOP 基礎概念

上面的解釋偏官方,下面用 “方言” 再給大家解釋一遍。

5 種通知的分類:

1.3 AOP 簡單示例

新建 Louzai 類:

@Data
@Service
public class Louzai {

    public void everyDay() {
        System.out.println("睡覺");
    }
}

添加 LouzaiAspect 切面:

@Aspect
@Component
public class LouzaiAspect {
    
    @Pointcut("execution(* com.java.Louzai.everyDay())")
    private void myPointCut() {
    }

    // 前置通知
    @Before("myPointCut()")
    public void myBefore() {
        System.out.println("喫飯");
    }

    // 後置通知
    @AfterReturning(value = "myPointCut()")
    public void myAfterReturning() {
        System.out.println("打豆豆。。。");
    }
}

applicationContext.xml 添加:

<!--啓用@Autowired等註解-->
<context:annotation-config/>
<context:component-scan base-package="com" />
<aop:aspectj-autoproxy proxy-target-class="true"/>

程序入口:

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context =new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        Louzai louzai = (Louzai) context.getBean("louzai");
        louzai.everyDay();
    }
}

輸出:

喫飯
睡覺
打豆豆。。。

這個示例非常簡單,“睡覺” 加了前置和後置通知,但是 Spring 在內部是如何工作的呢?

1.4 Spring AOP 工作流程

爲了方便大家能更好看懂後面的源碼,我先整體介紹一下源碼的執行流程,讓大家有一個整體的認識,否則容易被繞進去。

整個 Spring AOP 源碼,其實分爲 3 塊,我們會結合上面的示例,給大家進行講解。

第一塊就是前置處理,我們在創建 Louzai Bean 的前置處理中,會遍歷程序所有的切面信息,然後將切面信息保存在緩存中,比如示例中 LouzaiAspect 的所有切面信息。

第二塊就是後置處理,我們在創建 Louzai Bean 的後置處理器中,裏面會做兩件事情:

第三塊就是執行切面,通過 “責任鏈 + 遞歸”,去執行切面。

  1. 源碼解讀 =======

注意:Spring 的版本是 5.2.15.RELEASE,否則和我的代碼不一樣!!!

除了原理部分,上面的知識都不難,下面纔是我們的重頭戲,讓你跟着樓仔,走一遍代碼流程。

2.1 代碼入口

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

進入 doGetBean(),進入創建 Bean 的邏輯。

2.2 前置處理

主要就是遍歷切面,放入緩存。

這裏是重點!敲黑板!!!

  1. 我們會先遍歷所有的類;

  2. 判斷是否切面,只有切面纔會進入後面邏輯;

  3. 獲取每個 Aspect 的切面列表;

  4. 保存 Aspect 的切面列表到緩存 advisorsCache 中。

到這裏,獲取切面信息的流程就結束了,因爲後續對切面數據的獲取,都是從緩存 advisorsCache 中拿到。

下面就對上面的流程,再深入解讀一下。

2.2.1 判斷是否是切面

上圖的第 2 步,邏輯如下:

2.2.2 獲取切面列表

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

2.3 後置處理

主要就是從緩存拿切面,和 louzai 的方法匹配,並創建 AOP 代理對象。

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

這裏是重點!敲黑板!!!

  1. 先獲取 louzai 類的所有切面列表;

  2. 創建一個 AOP 的代理對象。

2.3.1 獲取切面

我們先進入第一步,看是如何獲取 louzai 的切面列表。

進入 buildAspectJAdvisors(),這個方法應該有印象,就是前面將切面信息放入緩存 advisorsCache 中,現在這裏就是要獲取緩存。

再回到 findEligibleAdvisors(),從緩存拿到所有的切面信息後,繼續往後執行。

2.3.2 創建代理對象

有了 louzai 的切面列表,後面就可以開始去創建 AOP 代理對象。

這裏是重點!敲黑板!!!

這裏有 2 種創建 AOP 代理對象的方式,我們是選用 Cglib 來創建。

我們再回到創建代理對象的入口,看看創建的代理對象。

2.4 切面執行

通過 “責任鏈 + 遞歸”,執行切面和方法。

前方高能!這塊邏輯非常複雜!!!

下面就是 “執行切面” 最核心的邏輯,簡單說一下設計思路:

  1. 設計思路:採用遞歸 + 責任鏈的模式;

  2. 遞歸:反覆執行 CglibMethodInvocation 的 proceed();

  3. 退出遞歸條件:interceptorsAndDynamicMethodMatchers 數組中的對象,全部執行完畢;

  4. 責任鏈:示例中的責任鏈,是個長度爲 3 的數組,每次取其中一個數組對象,然後去執行對象的 invoke()。

因爲我們數組裏面只有 3 個對象,所以只會遞歸 3 次,下面就看這 3 次是如何遞歸,責任鏈是如何執行的,設計得很巧妙!

2.4.1 第一次遞歸

數組的第一個對象是 ExposeInvocationInterceptor,執行 invoke(),注意入參是 CglibMethodInvocation。

裏面啥都沒幹,繼續執行 CglibMethodInvocation 的 process()。

2.4.2 第二次遞歸

數組的第二個對象是 MethodBeforeAdviceInterceptor,執行 invoke()。

2.4.3 第三次遞歸

數組的第二個對象是 AfterReturningAdviceInterceptor,執行 invoke()。

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

再回到第三次遞歸的入口,繼續執行後面的切面。

切面執行邏輯,前面已經演示過,直接看執行方法。

後面就依次退出遞歸,整個流程結束。

2.4.4 設計思路

這塊代碼,我研究了大半天,因爲這個不是純粹的責任鏈模式。

純粹的責任鏈模式,對象內部有一個自身的 next 對象,執行完當前對象的方法末尾,就會啓動 next 對象的執行,直到最後一個 next 對象執行完畢,或者中途因爲某些條件中斷執行,責任鏈纔會退出。

這裏 CglibMethodInvocation 對象內部沒有 next 對象,全程是通過 interceptorsAndDynamicMethodMatchers 長度爲 3 的數組控制,依次去執行數組中的對象,直到最後一個對象執行完畢,責任鏈纔會退出。

這個也屬於責任鏈,只是實現方式不一樣,後面會詳細剖析,下面再討論一下,這些類之間的關係。

我們的主對象是 CglibMethodInvocation,繼承於 ReflectiveMethodInvocation,然後 process() 的核心邏輯,其實都在 ReflectiveMethodInvocation 中。

ReflectiveMethodInvocation 中的 process() 控制整個責任鏈的執行。

ReflectiveMethodInvocation 中的 process() 方法,裏面有個長度爲 3 的數組 interceptorsAndDynamicMethodMatchers,裏面存儲了 3 個對象,分別爲 ExposeInvocationInterceptor、MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor。

注意!!!這 3 個對象,都是繼承 MethodInterceptor 接口。

然後每次執行 invoke() 時,裏面都會去執行 CglibMethodInvocation 的 process()。

是不是聽得有些蒙圈?甭着急,我重新再幫你梳理一下。

對象和方法的關係:

可能有同學會說,invoke() 的入參是 MethodInvocation,沒錯!但是 CglibMethodInvocation 也繼承了 MethodInvocation,不信自己可以去看。

執行邏輯:

所以這裏設計巧妙的地方,是因爲純粹責任鏈模式,裏面的 next 對象,需要保證裏面的對象類型完全相同。

但是數組裏面的 3 個對象,裏面沒有 next 成員對象,所以不能直接用責任鏈模式,那怎麼辦呢?就單獨搞了一個 CglibMethodInvocation.process(),通過去無限遞歸 process(),來實現這個責任鏈的邏輯。

這就是我們爲什麼要看源碼,學習裏面優秀的設計思路!

  1. 總結 =====

我們再小節一下,文章先介紹了什麼是 AOP,以及 AOP 的原理和示例。

之後再剖析了 AOP 的源碼,分爲 3 塊:

這篇文章,是 Spring 源碼解析的第 3 篇,也是感覺最難的一篇,光圖解代碼就扣了 6 個小時,整個人都被扣麻了。

最難的地方還不是摳圖,而是 “切面執行” 的設計思路,雖然流程能走通,但是把整個設計思想能總結出來,並講得能讓大家明白,還是非常不容易的。

今天的源碼解析就到這,Spring 相關的源碼,還有哪些是大家想學習的呢,可以給樓仔留言。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/7lFnphDaYqhgc0ZTO34s0Q