SpringAOP高頻知識梳理
Spring AOP原理深層解析
前言
IOC和AOP是Spring的兩個重要組成部分,IOC之前也經過分析(點選跳轉)可以抽象認為這是一個容器,那AOP又是什麼東西呢?
AOP是Aspect-Oriented Programming(面向方面程式設計或者面向切面)的簡稱。它可以看成是OOP(面向物件程式設計)的一種延續。簡單地說就是將程式碼中重複的部分抽取出來,在需要執行的時候使用動態代理技術,在不修改原始碼的基礎上對方法進行增強。
AOP的知識點特別多,這裡簡單整理一些比較重要的知識點概念。如果需要深度學習下去,可以參考原始碼分析:推薦閱讀
什麼是AOP?
(這裡主要是摘錄https://gyl-coder.top/spring/spring-ioc-aop/)
下面我們先看一個 OOP 的例子。
例如:現有三個類,Horse
、Pig
、Dog
,這三個類中都有 eat 和 run 兩個方法。
通過 OOP 思想中的繼承,我們可以提取出一個 Animal 的父類,然後將 eat 和 run 方法放入父類中,Horse
、Pig
、Dog
通過繼承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
註解使用。
- 前置通知(Before Advice): 在連線點之前執行的Advice,不過除非它丟擲異常,否則沒有能力中斷執行流。使用
織入(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,我們可以將我們與業務無關的程式碼,分割出來。具體的應用場景主要是有這些:
- 許可權控制
- 快取控制
- 事務控制
- 審計日誌
- 效能監控
- 分散式追蹤
- 異常處理
如何在這些場景去使用就需要根據情況具體實踐了。可以多看看實戰書籍!
參考資料
https://gyl-coder.top/spring/spring-ioc-aop/
《Spring技術內幕:深入解析Spring架構與原理》