1. 程式人生 > 實用技巧 >SpringAOP高頻知識梳理

SpringAOP高頻知識梳理

Spring AOP原理深層解析

前言

IOC和AOP是Spring的兩個重要組成部分,IOC之前也經過分析(點選跳轉)可以抽象認為這是一個容器,那AOP又是什麼東西呢?

AOP是Aspect-Oriented Programming(面向方面程式設計或者面向切面)的簡稱。它可以看成是OOP(面向物件程式設計)的一種延續。簡單地說就是將程式碼中重複的部分抽取出來,在需要執行的時候使用動態代理技術,在不修改原始碼的基礎上對方法進行增強。

AOP的知識點特別多,這裡簡單整理一些比較重要的知識點概念。如果需要深度學習下去,可以參考原始碼分析:推薦閱讀

什麼是AOP?

(這裡主要是摘錄https://gyl-coder.top/spring/spring-ioc-aop/)

下面我們先看一個 OOP 的例子。

例如:現有三個類,HorsePigDog,這三個類中都有 eat 和 run 兩個方法。

通過 OOP 思想中的繼承,我們可以提取出一個 Animal 的父類,然後將 eat 和 run 方法放入父類中,HorsePigDog通過繼承Animal類即可自動獲得 eat()run() 方法。這樣將會少些很多重複的程式碼。

OOP 程式設計思想可以解決大部分的程式碼重複問題。但是有一些問題是處理不了的。比如在父類 Animal 中的多個方法的相同位置出現了重複的程式碼,OOP 就解決不了。

/**
 * 動物父類
 */
public class Animal {

    /** 身高 */
    private String height;

    /** 體重 */
    private double weight;

    public void eat() {
        // 效能監控程式碼
        long start = System.currentTimeMillis();

        // 業務邏輯程式碼
        System.out.println("I can eat...");

        // 效能監控程式碼
        System.out.println("執行時長:" + (System.currentTimeMillis() - start)/1000f + "s");
    }

    public void run() {
        // 效能監控程式碼
        long start = System.currentTimeMillis();

        // 業務邏輯程式碼
        System.out.println("I can run...");

        // 效能監控程式碼
        System.out.println("執行時長:" + (System.currentTimeMillis() - start)/1000f + "s");
    }
}

這部分重複的程式碼,一般統稱為 橫切邏輯程式碼

橫切邏輯程式碼存在的問題:

  • 程式碼重複問題
  • 橫切邏輯程式碼和業務程式碼混雜在一起,程式碼臃腫,不變維護

AOP 就是用來解決這些問題的

AOP 另闢蹊徑,提出橫向抽取機制,將橫切邏輯程式碼和業務邏輯程式碼分離

程式碼拆分比較容易,難的是如何在不改變原有業務邏輯的情況下,悄無聲息的將橫向邏輯程式碼應用到原有的業務邏輯中,達到和原來一樣的效果。

AOP 解決了什麼問題

通過上面的分析可以發現,AOP 主要用來解決:在不改變原有業務邏輯的情況下,增強橫切邏輯程式碼,根本上解耦合,避免橫切邏輯程式碼重複。

AOP 為什麼叫面向切面程式設計

:指的是橫切邏輯,原有業務邏輯程式碼不動,只能操作橫切邏輯程式碼,所以面向橫切邏輯

:橫切邏輯程式碼往往要影響的是很多個方法,每個方法如同一個點,多個點構成一個面。這裡有一個面的概念

重要術語

連線點(Join point):

  • 能夠被攔截的地方:Spring AOP是基於動態代理的,所以是方法攔截的。每個成員方法都可以稱之為連線點~

切點(Poincut):

  • 具體定位的連線點:上面也說了,每個方法都可以稱之為連線點,我們具體定位到某一個方法就成為切點

增強/通知(Advice):

  • 表示新增到切點的一段邏輯程式碼,並定位連線點的方位資訊

    • 簡單來說就定義了是幹什麼的,具體是在哪幹
    • Spring AOP提供了5種Advice型別給我們:前置、後置、返回、異常、環繞給我們使用!
  • 五種Advice型別如下:

    • 前置通知(Before Advice): 在連線點之前執行的Advice,不過除非它丟擲異常,否則沒有能力中斷執行流。使用 @Before 註解使用這個Advice。
    • 返回之後通知(After Retuning Advice): 在連線點正常結束之後執行的Advice。例如,如果一個方法沒有丟擲異常正常返回。通過 @AfterReturning 關注使用它。
    • 丟擲(異常)後執行通知(After Throwing Advice): 如果一個方法通過丟擲異常來退出的話,這個Advice就會被執行。通用 @AfterThrowing 註解來使用。
    • 後置通知(After Advice): 無論連線點是通過什麼方式退出的(正常返回或者丟擲異常)都會執行在結束後執行這些Advice。通過 @After 註解使用。
    • 圍繞通知(Around Advice): 圍繞連線點執行的Advice,就你一個方法呼叫。這是最強大的Advice。通過 @Around 註解使用。

織入(Weaving):

  • 增強/通知新增到目標類的具體連線點上的過程。

引入/引介(Introduction):

  • 引入/引介允許我們向現有的類新增新方法或屬性。是一種特殊的增強!

切面(Aspect):

  • 切面由切點和增強/通知組成,它既包括了橫切邏輯的定義、也包括了連線點的定義。

底層實現

我們上面介紹了AOP的一系列相關術語,而這些術語其實也就是為了加深我們對AOP的面向切面的認識。AOP的底層其實是通過動態代理來實現的,其實如果學過設計模式其實看著AOP的思想也能大概猜出來了。將相同邏輯的重複程式碼橫向抽取出來,使用動態代理技術將這些重複程式碼織入到目標物件方法中,實現和原來一樣的功能

在SpringAOP中,我們底層的通過兩種動態代理來實現的,分別是JDK動態代理和CGLib動態代理。

JDK動態代理

關於JDK的動態代理程式碼演示可以看一下我的這一篇,順便也可以瞭解一下動態代理模式(點選跳轉

JDK動態代理是需要實現某個介面了,而我們類未必全部會有介面,於是CGLib代理就有了~~

  • CGLib代理其生成的動態代理物件是目標類的子類
  • Spring AOP預設是使用JDK動態代理,如果代理的類沒有介面則會使用CGLib代理。

那麼JDK代理和CGLib代理我們該用哪個呢??在《精通Spring4.x 企業應用開發實戰》給出了建議:

  • 如果是單例的我們最好使用CGLib代理,如果是多例的我們最好使用JDK代理

原因:

  • JDK在建立代理物件時的效能要高於CGLib代理,而生成代理物件的執行效能卻比CGLib的低。
  • 如果是單例的代理,推薦使用CGLib

CGLib動態代理

位元組碼生成技術實現AOP,其實就是繼承被代理物件,然後Override需要被代理的方法,在覆蓋該方法時,自然是可以插入我們自己的程式碼的。CGLib動態代理需要依賴asm包,把被代理物件類的class檔案載入進來,修改其位元組碼生成子類。

因為需要Override被代理物件的方法,所以自然CGLIB技術實現AOP時,就 必須要求需要被代理的方法不能是final方法,因為final方法不能被子類覆蓋 。

現在演示一下如何使用CGLIB動態代理實現開頭的情景,CGLIB動態代理不要求被代理類實現介面,先寫一個被代理類。

public class MonkeyOperation {
    public void put() {
        System.out.println("放入猴子...");
    }
 
    public void get() {
        System.out.println("拿出猴子...");
    }
}

在寫一個類實現MethodInterceptor介面,並在介面方法intercept()裡對被代理物件的方法做增強,並編寫生成代理物件的方法

public class FridgeCGLibProxy implements MethodInterceptor {
 
    public String name="hahaha";
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        openDoor();//呼叫被代理方法做一些操作
        Object result = methodProxy.invokeSuper(proxy,args);//執行被代理物件的方法,如果方法有返回值則賦值給result
        closeDoor();//呼叫被代理方法後做一些操作
        return result;
    }
    private void openDoor(){
        System.out.println("開啟冰箱...");
    }
    private void closeDoor(){
        System.out.println("關閉冰箱...");
    }
    public Object getProxy(Class cls){//引數為被代理的類物件
        Enhancer enhancer = new Enhancer();//建立增強器,用來建立動態代理類
        enhancer.setSuperclass(cls);//設定父類,即被代理的類物件
        enhancer.setCallback(this);//設定回撥,指定為當前物件
        return enhancer.create();//返回生成的代理類
    }
}

測試及結果:

  public static void main(String args[]) {
      MonkeyOperation monkeyOperation =(MonkeyOperation)new FridgeCGLibProxy().getProxy(MonkeyOperation.class);
      monkeyOperation.put();
      monkeyOperation.get();
  }

應用場景

通過使用SpringAOP,我們可以將我們與業務無關的程式碼,分割出來。具體的應用場景主要是有這些:

  1. 許可權控制
  2. 快取控制
  3. 事務控制
  4. 審計日誌
  5. 效能監控
  6. 分散式追蹤
  7. 異常處理

如何在這些場景去使用就需要根據情況具體實踐了。可以多看看實戰書籍!

參考資料

SpringAOP就是這麼簡單啦

https://gyl-coder.top/spring/spring-ioc-aop/

Spring中的AOP原理

《Spring技術內幕:深入解析Spring架構與原理》