SpringAOP專欄二《原理篇》

上一篇SpringAOP專欄一《使用教程篇》-CSDN博客介紹了SpringAop如何使用,這一篇文章就會介紹Spring AOP 的底層實現原理,并通過源代碼解析來詳細闡述其實現過程。

前言

Spring AOP 的實現原理是基于動態代理字節碼操作的。不了解動態代理和字節碼操作的讀者可以先看一下這篇文章java中的反射和代理模式-CSDN博客

實現原理

下面我會基于在使用SpringAOP進行邏輯增強時各個核心類的執行順序進行底層原理的剖析

實現代理和切面邏輯的核心類執行順序如下:

1.Bean 實例化階段:

  • BeanPostProcessor:在 Bean 實例化之后,進行初始化前的處理。其中,AbstractAutoProxyCreator 是一個重要的 BeanPostProcessor 實現類,用于自動創建代理對象。

2.切面織入階段:

  • AdvisedSupport:封裝了切面邏輯和目標對象信息。
  • ProxyFactoryBean:生成代理對象的工廠類。
  • AopProxyFactory:AOP 代理對象的工廠接口。
  • AopProxy:AOP 代理對象的核心接口,定義了獲取代理對象的方法。
  • CglibAopProxy:基于 CGLIB 的動態代理實現類。
  • DynamicAdvisedInterceptor:CGLIB 動態代理的回調函數實現類,用于觸發切面邏輯的執行。

3.方法攔截與執行階段:

  • ProxyMethodInvocation:代理方法調用的核心類,封裝了目標對象方法和參數信息。
  • ReflectiveMethodInvocation:反射調用目標方法的類,是 ProxyMethodInvocation 的具體實現類。
  • MethodInvocationInterceptor:方法攔截器接口,定義了攔截器的方法執行邏輯。
  • MethodInterceptor:CGLIB 庫中的接口,被 MethodInvocationInterceptor 實現。

Bean 實例化階段

在service bean的創建過程中(也就是getBean("service")),AOP通過BeanPostProcess后置處理器操作進行介入 分為2種情況:

  • 用戶自定義了targetSource,則bean的創建(實例化、填充、初始化)均由用戶負責,Spring Ioc不會在管該代理目標對象traget,這種情況基本上不會發生,很多人用了幾年Spring可能都不知道有它的存在
  • 正常情況下都是Spring Ioc完成代理對象target的實例化、填充、初始化。然后在初始化后置處理器中進行介入,對bean也就是service進行代理

下面是Bean示例化的流程圖

切面織入階段

Spring AOP 是構建在動態代理基礎上,因此?Spring 對 AOP 的支持局限于方法級別的攔截。

在這個階段會讀取相關的SpringAOP的配置,如何將切面邏輯和目標對象信息封裝到AdvisedSupport中,再通過ProxyFactoryBean(生成代理對象的工廠類)根據目標對象是否實現接口來調用JDK動態代理還是Cglib代理。

下面詳細介紹一下兩者代理方式的實現源碼:

Spring AOP 動態代理實現:

  • 默認情況下,實現了接?的類,使? AOP 會基于 JDK ?成代理類,沒有實現接?的類,會基于 CGLIB ?成代理類。
  • JDK Proxy(JDK 動態代理)
  • CGLIB Proxy:默認情況下 Spring AOP 都會采用 CGLIB 來實現動態代理,因為效率高
  • CGLIB 實現原理:通過繼承代理對象來實現動態代理的(子類擁有父類的所有功能)
  • CGLIB 缺點:不能代理最終類(也就是被 final 修飾的類)
    ?

JDK動態代理

????JDK 動態代理是 Java 自帶的動態代理實現方式。使用JDK動態代理時,需要目標對象實現至少一個接口。JDK 動態代理會在運行時生成一個實現了目標對象接口的代理類,該代理類會在目標對象方法執行前后插入切面代碼。

下面是JdkDynamicAopProxy類的部分源碼,感興趣的讀者可以自己去看看,我這里就不一一截圖給大家看了。

如果讀者看源碼如果有困難,可以看一下這個我簡化了的JdkDynamicAopProxy類。這個類我保留了主要邏輯,把一些提高代碼健壯性的部分去掉了。

JdkDynamicAopProxy 類的主要作用是將切面邏輯織入到目標對象的方法調用中。當使用基于接口的代理方式時,Spring AOP 使用 JDK 動態代理來創建代理對象。JdkDynamicAopProxy 類會根據配置的切面信息,動態地生成一個代理對象,并將切面邏輯織入到該代理對象的方法調用中。

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {private final AdvisedSupport advised;public JdkDynamicAopProxy(AdvisedSupport advised) {this.advised = advised;}@Overridepublic Object getProxy() {return Proxy.newProxyInstance(getClass().getClassLoader(),advised.getTargetSource().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MethodInterceptor methodInterceptor = advised.getMethodInterceptor();MethodInvocation methodInvocation = new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),method,args,methodInterceptor,advised.getTargetSource().getTargetClass());return methodInvocation.proceed();}
}

??在該代碼中,JdkDynamicAopProxy 類實現了 AopProxyInvocationHandler 接口。

AopProxy 接口是 Spring AOP 提供的代理接口,它定義了獲取代理對象的方法。JdkDynamicAopProxy 類通過實現該接口,提供了基于 JDK 動態代理的代理對象獲取功能。

InvocationHandler 接口是 JDK 提供的反射 API 中的一部分,它定義了一個 invoke 方法,用于在代理對象上調用被代理方法。JdkDynamicAopProxy 類實現了 InvocationHandler 接口,通過重寫 invoke 方法,實現對被代理方法的增強邏輯。

下面是對簡化了的JdkDynamicAopProxy類的詳細解讀

  1. AdvisedSupport 類型的屬性 advised:表示該代理對象所依賴的 AdvisedSupport 對象,該對象包含了切面配置信息。

  2. 構造函數 JdkDynamicAopProxy(AdvisedSupport advised):接收一個 AdvisedSupport 對象作為參數,并將其賦值給 advised 屬性。

  3. getProxy() 方法:實現了 AopProxy 接口中的方法,用于獲取代理對象。它通過調用 Proxy.newProxyInstance() 方法創建代理對象。

    • getClass().getClassLoader()?獲取當前類的類加載器。
    • advised.getTargetSource().getInterfaces()?獲取目標對象實現的接口數組。
    • this?表示使用當前對象作為代理對象的?InvocationHandler
  4. invoke(Object proxy, Method method, Object[] args) 方法:實現了 InvocationHandler 接口中的方法,攔截目標對象方法的調用并進行增強邏輯。

    • 首先,從?advised?對象中獲取?MethodInterceptor?實例,即切面邏輯。
    • 然后,創建一個?ReflectiveMethodInvocation?實例,傳入目標對象、目標方法、方法參數、切面邏輯和目標對象的類信息。
    • 最后,調用?methodInvocation.proceed()?方法執行切面邏輯,并返回方法的執行結果。

CGLIB 代理

????????CGLIB 代理是一個基于字節碼操作的代理方式,它可以為沒有實現接口的類創建代理對象。CGLIB 代理會在運行時生成一個目標對象的子類,并覆蓋其中的方法,以實現AOP的功能。

下面是CglibAopProxy類的部分源代碼,感興趣的讀者可以自己去看看,我這里就不一一截圖給大家看了。

如果讀者看源碼如果有困難,可以看一下這個我簡化了的CglibAopProxy類。這個類我保留了主要邏輯,對代碼進行了簡化。

public class CglibAopProxy implements AopProxy {private final AdvisedSupport advised;public CglibAopProxy(AdvisedSupport advised) {this.advised = advised;}@Overridepublic Object getProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(advised.getTargetSource().getTargetClass());enhancer.setCallback(new DynamicAdvisedInterceptor(advised));return enhancer.create();}private static class DynamicAdvisedInterceptor implements MethodInterceptor {private final AdvisedSupport advised;public DynamicAdvisedInterceptor(AdvisedSupport advised) {this.advised = advised;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {MethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(),method,args,proxy,advised.getMethodInterceptor(),advised.getTargetSource().getTargetClass());return methodInvocation.proceed();}}
}

CglibAopProxy 類主要有以下作用

  • 生成代理對象:CglibAopProxy 根據目標對象和切面邏輯生成一個代理對象。與 JDK 動態代理不同,CGLIB 代理不需要目標對象實現接口。

  • 增強方法邏輯:通過繼承目標對象,CglibAopProxy 重寫目標對象的方法,并在方法中添加切面邏輯。這樣,在調用代理對象的方法時,會先執行切面邏輯,然后再調用目標對象的原始方法。

  • 攔截方法調用:CglibAopProxy 使用 MethodInterceptor 接口來定義切面邏輯,并將其應用于生成的代理對象。在代理對象的方法調用過程中,切面邏輯會被觸發,從而實現橫切關注點的功能,如事務管理、日志記錄等。

  • 高性能的代理:相較于 JDK 動態代理,CGLIB 代理使用了字節碼生成技術,生成的代理對象是目標對象的子類。這種方式避免了通過反射調用目標對象的方法,提供了更高的執行性能。

下面是CglibAopProxy類的詳細解讀

  1. CglibAopProxy 類實現了 AopProxy 接口,該接口定義了獲取代理對象的方法 getProxy()。

  2. CglibAopProxy 類的構造方法需要一個 AdvisedSupport 對象作為參數,AdvisedSupport 是 Spring AOP 框架中的核心類,用于保存切面邏輯和目標對象信息。

  3. getProxy() 方法中,首先實例化了 Enhancer 對象,Enhancer 是 CGLIB 庫中的主要類,用于生成代理對象。

  4. setSuperclass() 方法將目標對象的類設置為要生成的代理類的父類,這樣代理類就可以繼承目標類的所有非私有方法。

  5. setCallback() 方法用于設置代理類的回調函數,即在代理類的方法調用時,會觸發回調函數中的邏輯。

  6. DynamicAdvisedInterceptor 類實現了 MethodInterceptor 接口,這個接口是 CGLIB 庫中的接口,用于定義代理類的回調函數。

  7. intercept() 方法是 DynamicAdvisedInterceptor 類的核心方法。當代理類的方法被調用時,intercept() 方法會被觸發。在該方法中,首先將目標對象的方法和參數封裝成 MethodInvocation 對象,然后調用其 proceed() 方法,從而觸發切面邏輯的執行。

  8. CglibMethodInvocation 類是 MethodInvocation 接口的實現類,用于封裝目標對象的方法和參數信息。

方法攔截與執行階段

該階段的實際執行流程為:

  1. 當代理對象的方法被調用時,會創建一個 ProxyMethodInvocation 對象,并將目標對象、目標方法、方法參數等信息傳遞給它。

  2. ProxyMethodInvocation 繼承了 ReflectiveMethodInvocation 類,因此它可以通過反射調用目標方法。

  3. 在攔截器鏈的創建過程中,AdvisorChainFactory 會根據切點和通知創建 Advisor 鏈,即將所有與目標方法匹配的切面的方法攔截器添加到攔截器鏈中。

  4. 當 ProxyMethodInvocation 執行目標方法時,它會依次遍歷攔截器鏈中的每個方法攔截器。

  5. 每個方法攔截器在方法調用前后執行自己的邏輯,可以實現前置通知、后置通知、異常處理和返回通知等功能。

  6. 方法攔截器的執行順序與它們在攔截器鏈中的順序一致。在方法調用之前,攔截器依次執行前置通知;在方法調用之后,攔截器依次執行后置通知;如果方法發生異常,攔截器執行異常處理邏輯;最后,攔截器執行返回通知。

下面我來分析一下SpringAOP的攔截器

SpringAOP的攔截器

我們先來了解一下攔截器的執行屬性:如下圖所示

接下來看一下AOP攔截器執行原理,攔截器是如何保證不同通知注解下的方法的執行順序的呢?

在 Spring AOP 中,攔截器鏈的執行順序是由 AdvisedSupport 類中的方法獲取的。AdvisedSupport 包含了代理對象需要的所有信息,包括目標對象、代理接口、攔截器等。其中,攔截器鏈的創建和執行是在 ExposeInvocationInterceptor 這個攔截器中完成的。

在創建攔截器鏈時,AdvisorChainFactory 會根據切面的順序將各個切面的攔截器順序組合成一個攔截器鏈。這樣就可以保證 before 在 after 之前執行,因為在創建攔截器鏈的過程中,會按照切面的順序將各個切面的攔截器依次添加到鏈中,最終形成一個有序的攔截器鏈。

另外,對于同一個方法的多個切面,Spring AOP 會根據切面的順序將它們的攔截器依次添加到攔截器鏈中,從而保證了它們的執行順序。這樣就可以保證 find 方法的執行順序符合切面的定義順序。

因此,通過 Spring AOP 框架內部對攔截器鏈的創建和執行機制的設計,可以保證攔截器的執行順序滿足業務需求,確保了 before 在 after 之前執行,并且保證了多個攔截器的執行順序。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/207697.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/207697.shtml
英文地址,請注明出處:http://en.pswp.cn/news/207697.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【C語言】函數遞歸詳解(一)

目錄 1.什么是遞歸: 1.1遞歸的思想: 1.2遞歸的限制條件: 2.遞歸舉例: 2.1舉例1:求n的階乘: 2.1.1 分析和代碼實現: 2.1.2圖示遞歸過程: 2.2舉例2:順序打印一個整數的…

機器學習---集成學習的初步理解

1. 集成學習 集成學習(ensemble learning)是現在非常火爆的機器學習方法。它本身不是一個單獨的機器學 習算法,而是通過構建并結合多個機器學習器來完成學習任務。也就是我們常說的“博采眾長”。集 成學習可以用于分類問題集成,回歸問題集成&#xff…

多線程并發Ping腳本

1. 前言 最近需要ping地址,還是挺多的,就使用python搞一個ping腳本,記錄一下,以免丟失了。 2. 腳本介紹 首先檢查是否存在True.txt或False.txt文件,并在用戶確認后進行刪除,然后從IP.txt的文件中讀取IP地…

CSS——sticky定位

1. 大白話解釋sticky定位 粘性定位通俗來說,它就是相對定位relative和固定定位fixed的結合體,它的觸發過程分為三個階段 在最近可滾動容器沒有觸發滑動之前,sticky盒子的表現為相對定位relative【第一階段】, 但當最近可滾動容…

【MATLAB】tvfEMD信號分解+FFT+HHT組合算法

有意向獲取代碼,請轉文末觀看代碼獲取方式~也可轉原文鏈接獲取~ 1 基本定義 TVFEMDFFTHHT組合算法是一種結合了總體變分模態分解(TVFEMD)、傅里葉變換(FFT)和希爾伯特-黃變換(HHT)的信號分解方…

vivado時序方法檢查8

TIMING-30 &#xff1a; 生成時鐘所選主源管腳欠佳 生成時鐘 <clock_name> 所選的主源管腳欠佳 &#xff0c; 時序可能處于消極狀態。 描述 雖然 create_generated_clock 命令允許您指定任意參考時鐘 &#xff0c; 但是生成時鐘應引用在其直接扇入中傳輸的時鐘。此…

電子學會C/C++編程等級考試2021年06月(五級)真題解析

C/C++等級考試(1~8級)全部真題?點這里 第1題:數字變換 給定一個包含5個數字(0-9)的字符串,例如 “02943”,請將“12345”變換到它。 你可以采取3種操作進行變換 1. 交換相鄰的兩個數字 2. 將一個數字加1。如果加1后大于9,則變為0 3. 將一個數字加倍。如果加倍后大于…

JS--異步的日常用法

目錄 JS 異步編程并發&#xff08;concurrency&#xff09;和并行&#xff08;parallelism&#xff09;區別回調函數&#xff08;Callback&#xff09;GeneratorPromiseasync 及 await常用定時器函數 JS 異步編程 并發&#xff08;concurrency&#xff09;和并行&#xff08;p…

Python中一些有趣的例題

下面會寫一些基礎的例題&#xff0c;有興趣的自己也可以練練手&#xff01; 1.假設手機短信收到的數字驗證碼為“278902”&#xff0c;編寫一個程序&#xff0c;讓用戶輸入數字驗證碼&#xff0c;如果數字驗證碼輸入正確&#xff0c;提示“支付成功”&#xff1b;否則提示“數…

Python configparser 模塊:優雅處理配置文件的得力工具

更多資料獲取 &#x1f4da; 個人網站&#xff1a;ipengtao.com 配置文件在軟件開發中扮演著重要的角色&#xff0c;而Python中的 configparser 模塊提供了一種優雅而靈活的方式來處理各種配置需求。本文將深入介紹 configparser 模塊的各個方面&#xff0c;通過豐富的示例代碼…

嵌入式雜記 - MDK的Code, RO-data , RW-data, ZI-data意思

嵌入式雜記 - Keil的Code, RO-data , RW-data, ZI-data意思 MDK中的數據分類MCU中的內部存儲分布MDK中數據類型存儲Code代碼段例子 RO-data 只讀數據段例子 RW-data 可讀寫數據段例子 ZI-data 清零數據段例子 在嵌入式開發中&#xff0c;我們經常都會使用一些IDE&#xff0c;例…

Hadoop學習筆記(HDP)-Part.17 安裝Spark2

目錄 Part.01 關于HDP Part.02 核心組件原理 Part.03 資源規劃 Part.04 基礎環境配置 Part.05 Yum源配置 Part.06 安裝OracleJDK Part.07 安裝MySQL Part.08 部署Ambari集群 Part.09 安裝OpenLDAP Part.10 創建集群 Part.11 安裝Kerberos Part.12 安裝HDFS Part.13 安裝Ranger …

Web前端 ---- 【Vue】Vuex的使用(輔助函數、模塊化開發)

目錄 前言 Vuex是什么 Vuex的配置 安裝vuex 配置vuex文件 Vuex核心對象 actions mutations getters state Vuex在vue中的使用 輔助函數 Vuex模塊化開發 前言 本文介紹一種新的用于組件傳值的插件 —— vuex Vuex是什么 Vuex 是一個專為 Vue.js 應用程序開發的狀態…

淺談前端代碼里的命名規范與注釋

淺談前端代碼里的命名規范與注釋 在前端代碼中&#xff0c;命名規范和注釋是非常重要的。它們不僅有助于提高代碼的可讀性和可維護性&#xff0c;還可以幫助開發者之間更好地協作和溝通。下面是一些關于命名規范和注釋的常見建議&#xff1a; 命名規范&#xff1a; 使用有意義…

【ArcGIS Pro微課1000例】0053:基于SQL Server創建與啟用地理數據庫

之前的文章有講述基于SQL Server創建企業級地理數據庫,本文講述在SQL Server中創建常規的關心數據庫,然后在ArcGIS Pro中將其啟用,轉換為企業級地理數據庫。 1. 在SQL Server中創建數據庫** 打開SQL Server 2019,連接到數據庫服務器。 展開數據庫連接,在數據庫上右鍵→新…

python中的lambda關鍵字

對于一切很模糊的知識&#xff0c;首要的是抓住概念的定義。 lambda&#xff1a;在 Python 中用于創建匿名函數的關鍵字。 也即&#xff0c;lambda是一種關鍵字&#xff0c;這種關鍵字的作用是創建匿名函數。 這一段很好懂&#xff0c;就是匿名函數有點懵。 什么是匿名函數&…

mybatis-plus構造器查詢

文章目錄 Hutool工具包Vo與entity轉換多表分頁查詢構造器&#xff1a;查詢構造器&#xff1a;拼接構造器&#xff1a;刪除操作構造器&#xff1a;修改操作查詢關鍵字 Hutool工具包Vo與entity轉換 BeanUtils&#xff1a;copyProperties(vo, entity)&#xff0c;vo轉實體類。 L…

在裝有 PostgreSQL 14 的 Red Hat8上安裝 `pg_cron`

要在裝有 PostgreSQL 14 的 Red Hat、CentOS、Fedora 或 Amazon Linux 上安裝 pg_cron&#xff0c;請遵循以下步驟。這些步驟假定您已經安裝了 PostgreSQL Global Development Group (PGDG) 的 PostgreSQL 版本。 安裝 pg_cron 擴展 使用 yum 安裝 pg_cron 擴展&#xff1a;s…

(四)Tiki-taka算法(TTA)求解無人機三維路徑規劃研究(MATLAB)

一、無人機模型簡介&#xff1a; 單個無人機三維路徑規劃問題及其建模_IT猿手的博客-CSDN博客 參考文獻&#xff1a; [1]胡觀凱,鐘建華,李永正,黎萬洪.基于IPSO-GA算法的無人機三維路徑規劃[J].現代電子技術,2023,46(07):115-120 二、Tiki-taka算法&#xff08;TTA&#xf…

基于SSH的java記賬管理系統

基于SSH的java記賬管理系統 一、系統介紹二、功能展示四、其他系統實現五、獲取源碼 一、系統介紹 項目類型&#xff1a;Java EE項目 項目名稱&#xff1a;基于SSH的記賬管理系統 項目架構&#xff1a;B/S架構 開發語言&#xff1a;Java語言 前端技術&#xff1a;HTML、CS…