aop實現原理_從宏觀的實現原理和設計本質入手,帶你理解 AOP 框架的原理

點擊上方“Java知音”,選擇“置頂公眾號”

技術文章第一時間送達!

作者:FeelsChaotic

juejin.im/post/5c57b2d5e51d457ffd56ffbb

前言

本文將從另一個角度講解 AOP,從宏觀的實現原理和設計本質入手。大部分講 AOP 的博文都是一上來就羅列語法,然后敲個應用 demo就完了 。但學習不能知其然,不知其所以然。

對 AOP 我提出了幾點思考:

  • AspectJ 為什么會大熱?

  • AspectJ 是怎樣工作的?

  • 和 Spring AOP 有什么區別?

  • 什么場景下適用我們能不能自己實現一個 AOP 方法?

一、引入

敲一個小 Demo 來引入主題,假設我想不依賴任何 AOP 方法,在特定方法的執行前后加上日志打印。

第一種方式:寫死代碼

定義一個目標類接口

74831fc2eb1679fff71d9253b18b77a9.png

ce2e55bc45baf84b277496a1876acb92.png

把 before() 和 after() 方法寫死在 execute() 方法體中,非常不優雅,我們改進一下。

第二種方式:靜態代理

3a49ca581f30c4754f68f0b115da9d48.png

但是存在一個問題,隨著打印日志的需求增多,Proxy 類越來越多,我們能不能保持只有一個代理呢?這時候我們就需要用到 JDK 動態代理了。

第三種方式:動態代理

新建動態代理類

8e1cf1126e939b97a1e31eb2f5c07432.png

客戶端調用

951d3c3a056d5a6cfd7c57a5dfd72a93.png

這又引出一個問題,日志打印和業務邏輯耦合在一起,我們希望把前置和后置抽離出來,作為單獨的增強類。

第四種方式:動態代理 + 分離增強類

新建增強類接口和實現類

13b7b877a77b27bf446f5e2c217188fb.png

用反射代替寫死方法,解耦代理和操作者

0b62b5b737e0ec4139282b237bcbd83f.png

客戶端調用

8b80d5d711713054c9be8a542279ab2c.png

但是用了反射性能太差了,而且動態代理用起來也不方便,有沒有更好的辦法?

我們發現 Demo 存在種種問題

  • 靜態代理每次都要自己新建個代理類,太繁瑣,重用性又差,一個代理不能同時代理多種類;

  • 動態代理可以重用,但性能太差;

  • 代理類耦合進被代理類的調用階段,萬一我需要改下 before、after 的方法名,可能會點燃一個炸彈;

  • 代理攔截了一個類,就會攔截這個類的所有方法,難道我還要在代理類里加個 if-else 判斷特定方法過濾攔截?我們可以不可以只攔截特定的方法?

  • 如果我既要打印日志,又要計算方法執行用時,每次都要去改增強類嗎?

我們的訴求很簡單:1. 性能高;2. 松耦合;3. 步驟方便;4. 靈活性高。

那主流的 AOP 框架是怎么解決這個問題的呢?我們趕緊來看看!

二、AOP 方法

不同的 AOP 方法原理略微有些不同,我們先看下 AOP 實現方式有哪些:

208b323450ba598dfe00a35c2293d2f6.png

所有 AOP 方法本質就是:攔截、代理、反射(動態情況下),實現原理可以看作是代理 / 裝飾設計模式的泛化,為什么這么說?我們來詳細分析一下。Java:由淺入深揭開 AOP 實現原理

三、靜態織入原理,以 AspectJ 為例

靜態織入原理就是靜態代理,我們以 AspectJ 為例。

1. AspectJ 設計思路

前面說到 Demo 存在的種種問題,AspectJ 是怎么解決的呢?AspectJ 提供了兩套強大的機制:

(1)切面語法 | 解決業務和切面的耦合

AspectJ 中的切面,就解決了這個問題。

@Before("execution(*?android.view.View.OnClickListener.onClick(..))")

我們可以通過切面,將增強類與攔截匹配條件(切點)組合在一起,從而生成代理。這把是否要使用切面的決定權利還給了切面,我們在寫切面時就可以決定哪些類的哪些方法會被代理,從而邏輯上不需要侵入業務代碼。

而普通的代理模式并沒有做到切面與業務代碼的解耦,雖然將切面的邏輯獨立進了代理類,但是決定是否使用切面的權利仍然在業務代碼中。這才導致了 Demo 中種種的麻煩。

AspectJ 提供了兩套對切面的描述方法:

1.我們常用的基于 java 注解切面描述的方法,寫起來十分方便,兼容 Java 語法;

@Aspect
public?class?AnnoAspect?{
????@Pointcut("execution(...)")
????public?void?jointPoint()?{
????}

????@Before("jointPoint()")
????public?void?before()?{
????????//...
????}

????@After("jointPoint()")
????public?void?after()?{
????????//...
????}
}

2.基于 aspect 文件的切面描述方法,這種語法不兼容 Java 語法。

public?aspect?AnnoAspect?{

????pointcut?XX():execution(...);
????before():?XX()?{
????????//...
????}
????after():?XX()?{
????????//...
????}
}????

(2)織入工具 | 解決代理手動調用的繁瑣

那么切面語法讓切面從邏輯上與業務代碼解耦,但是我要怎么找到特定的業務代碼織入切面呢?

兩種解決思路:一種就是提供注冊機制,通過額外的配置文件指明哪些類受到切面的影響,不過這還是需要干涉對象創建的過程;另外一種解決思路就是在編譯期或類加載期先掃描切面,并將切面代碼通過某種形式插入到業務代碼中。

那 AspectJ 織入方式有兩種:一種是 ajc 編譯,可以在編譯期將切面織入到業務代碼中。另一種就是 aspectjweaver.jar 的 agent 代理,提供了一個 Java agent 用于在類加載期間織入切面。

2. 通過 class 反推 AspectJ 實現機制

(1)@Before 機制

國際慣例寫個 Demo

1.自定義 AutoLog 注解

04a756a12c85d5f21a655cc1ce64f5de.png

2.編寫 LogAspect 切面

402a471fc1bcc96bfcf58e03e1d03809.png

3.在切入點中加上注解

03dde4f5d45fc265bc6b75b1bb989379.png

反編譯后(請點開大圖查看)

a29db7be2654bfc48e3e859c71a5c66f.png

發現 AspectJ 會把調用切面的方法插入到切入點中,且封裝了切入點所在的方法名、所在類、入參名、入參值、返回值等等信息,傳遞給切面,這樣就建立了切面和業務代碼的關聯。

我們跟進 LogAspect.aspectOf().aroundJoinPoint(localJoinPoint); 一探究竟。

e778add93506fa3fbc79d9aeb4302dbe.png

我們發現了什么?其實 Before 和 After 的插入就是在匹配到的 JoinPoint 調用前后插入 Advise 方法,以此來達到攔截目標 JoinPoint 的作用。如下圖所示:

23592a0b68acb5d335f30b7267c5db1f.png

(2)@Around 機制

1.自定義 SingleClick 注解

9745d5a33bfbe54dc2569e36cbd93c3c.png

2.編寫 SingleClickAspect 切面

12f97312a08552f5046f5f836e9c04f0.png

3.業務方加上注解

94b494becfdaea1f919d309ff13d4461.png

打開編譯后的 class 文件(請點開大圖查看)

bb99dc993f55b191dd7caceff85cc11d.png

我們發現和 Before、After 織入不一樣了!前者的織入只是在匹配的 JoinPoint 前后插入 Advise 方法,僅僅是插入。而 Around 拆分了業務代碼和 Advise 方法,把業務代碼遷移到新函數中,通過一個單獨的閉包拆分來執行,相當于對目標 JoinPoint 進行了一個代理,所以 Around 情況下我們除了編寫切面邏輯,還需要手動調用 joinPoint.proceed() 來調用閉包執行原方法。

我們看下 proceed() 都做了些什么

ff235dccf0213cc001d769fe3c392897.png

那這個 arc 是什么?什么時候拿到的呢?

bef3ac9db3e612c3a07d9d23c4277fb8.png

繼續回溯

0e5dc6b1943b0045c811573ef79c9405.png

在 AroundClosure 閉包中,會把運行時對象和當前連接點 joinPoint 對象傳入,調用 linkClosureAndJoinPoint() 綁定兩端,這樣在 Around 中就可以通過 ProceedingJoinPoint.proceed() 調用 AroundClosure,進而調用到目標方法了。

那么一圖總結 Around 機制:

a2fde8e256317b1722966d5b1fb9690b.png

我們從 AspectJ 編譯后的 class 文件可以明顯看出執行的邏輯,proceed 方法就是回調執行被代理類中的方法。

所以 AspectJ 做的事情如下:

  • 首先從文件列表里取出所有的文件名,讀取文件,進行分析;

  • 掃描含有 aspect 的切面文件;

  • 根據切面中定義規則,攔截匹配的 JoinPoint ;

  • 繼續讀取切面定義的規則,根據 around 或 before ,采用不同策略織入切面。

(3)@Before @After 機制與 @Around 機制區別

  • Before、After 僅僅是織入了 Advise 方法

  • Around 使用了代理 + 閉包的方式進行替換

3. AspectJ 底層技術總結

分析完 class 你會發現,AspectJ 實際上就是用一種特定語言編寫切面,通過自己的語法編譯工具 ajc 編譯器來編譯,生成一個新的代理類,該代理類增強了業務類。

AspectJ 就是一個代碼生成工具;

編寫一段通用的代碼,然后根據 AspectJ 語法定義一套代碼生成規則,AspectJ 就會幫你把這段代碼插入到對應的位置去。Java知音擴展:代碼神器:拒絕重復編碼,這款IDEA插件了解一下.....

AspectJ 語法就是用來定義代碼生成規則的語法。

擴展編譯器,引入特定的語法來創建 Advise,從而在編譯期間就織入了Advise 的代碼。

如果使用過 Java Compiler Compiler (JavaCC),你會發現兩者的代碼生成規則的理念驚人相似。JavaCC 允許你在語法定義規則文件中,加入你自己的 Java 代碼,用來處理讀入的各種語法元素。

四、動態織入原理,以 Spring AOP 為例

動態織入原理就是動態代理。

1. Spring AOP 執行原理

Spring AOP 利用截取的方式,對被代理類進行裝飾,以取代原有對象行為的執行,不會生成新類。

2. Spring AOP VS AspectJ

可能有的小伙伴會困惑了,Spring AOP 使用了 AspectJ,怎么是動態代理呢?

那是因為 Spring 只是使用了與 AspectJ 一樣的注解,沒有使用 AspectJ 的編譯器,轉向采用動態代理技術的實現原理來構建 Spring AOP 的內部機制(動態織入),這是與 AspectJ(靜態織入)最根本的區別。

Spring 底層的動態代理分為兩種 JDK 動態代理和 CGLib:

JDK 動態代理用于對接口的代理,動態產生一個實現指定接口的類,注意動態代理有個約束:目標對象一定是要有接口的,沒有接口就不能實現動態代理,只能為接口創建動態代理實例,而不能對類創建動態代理。

CGLIB 用于對類的代理,把被代理對象類的 class 文件加載進來,修改其字節碼生成一個繼承了被代理類的子類。使用 cglib 就是為了彌補動態代理的不足。

3. JDK 動態代理的原理

我們前面的 Demo 第三種方式使用了動態代理,我們不禁有了疑問,動態代理類及其對象實例是如何生成的?調用動態代理對象方法為什么可以調用到目標對象方法?

e62bcbf854528f3df13bc642b1eb077e.png

我們通過 Proxy.newProxyInstance 可以動態生成指定接口的代理類的實例。我們來看下newProxyInstance內部實現機制。

fb9c902155a28c6cd81b78828caa93be.png

代理對象會實現接口的所有方法,實現的方法交由我們自定義的 handler 來處理。

56d2a7c5a207699d031198bdf5d1e75b.png

我們看下 getProxyClass0 方法,只憑一個類加載器、一個接口,是怎么創建代理類的?

d2a61ca90671ecdf386a92dcb9476a5e.png

注意一下:Android 中動態代理類是直接生成,而 Java 是生成代理類的字節碼,再根據字節碼生成代理類。

那么客戶端就可以 getProxy() 拿到生成的代理類?com.sun.proxy.$Proxy0

2c2580a175139fb156c3d64080baa674.png

這個代理類繼承自 Proxy 并實現了我們被代理類的所有接口,在各個接口方法的內部,通過反射調用了 InvocationHandlerImpl 的 invoke 方法。

總結下步驟:

  • 獲得被代理類的接口信息,生成一個實現了代理接口的動態代理類;

  • 通過反射獲得代理類的構造函數;

  • 利用構造函數生成動態代理類的實例對象,在調用具體方法前調用 invokeHandler 方法來處理。

36adfc7e6e06027e77f3b619ec564acb.png

后記

1. 設計模式不能脫離業務場景

不知不覺我們復習了一下代理模式,設計模式必須依賴大量的業務場景,脫離業務去看設計模式是沒有意義的。

因為脫離了應用場景,即使理解了模式的內容和結構,也學不會在合適的時候應用。

設計模式推薦:設計模式內容聚合

2. 敢于追求優雅的代碼

首先你要敢于追求優雅的代碼,就像我們開頭的打印日志的需求,不斷提出問題,不斷追求更好的解決方案,在新的方案上挖掘新的問題……如果你完全不追求設計,那自然是不會想到去研究設計模式的。

END

Java面試題專欄

【41期】盤點那些必問的數據結構算法題之鏈表【42期】盤點那些必問的數據結構算法題之二叉堆【43期】盤點那些必問的數據結構算法題之二叉樹基礎【44期】盤點那些必問的數據結構算法題之二分查找算法【45期】盤點那些必問的數據結構算法題之基礎排序【46期】盤點那些必問的數據結構算法題之快速排序【47期】六大類二叉樹面試題匯總解答【48期】盤點Netty面試常問考點:什么是 Netty 的零拷貝?【49期】面試官:SpringMVC的控制器是單例的嗎?【50期】基礎考察:ClassNotFoundException 和 NoClassDefFoundError 有什么區別

7485db5a60498fcd3c767ed68dfd2c63.png

我知道你 “在看72db43c5c75d995b86bd34d40137eff9.gif

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

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

相關文章

孟憲會老師推薦的一部C#圖解教程

Amazon五星級盛譽 C# 最新特性一覽無余 數十年開發與教學經驗的結晶 圖、表和文字完美結合,最易學的C# 教程 本書詳細信息:http://www.china-pub.com/43556 微軟4大名著評選結果揭曉:http://www.china-pub.com/static07/0812/jsj_micrmingz_0…

Python數據分析Numpy庫方法簡介(三)

補充: np.ceil()向上取整 3.1向上取整是4 np.floor()向下取整 數組名.resize((m,n)) 重置行列 基礎操作 np.random.randn()符合正態分布(鐘行/高斯)的數據 矩陣的水平拼接 np.vstack((a,b)) 矩陣的垂直拼接 np.hstack((a,b)) 點陣積: np.dot(a,b)/ ab…

xxl-job源碼分析

xxl-job源碼分析 xxl-job 系統說明 安裝 安裝部署參考文檔:分布式任務調度平臺xxl-job 功能 定時調度、服務解耦、靈活控制跑批時間(停止、開啟、重新設定時間、手動觸發) XXL-JOB是一個輕量級分布式任務調度平臺,其核心設計目標是…

定制jQuery File Upload為微博式單文件上傳

原文鏈接:http://avnpc.com/pages/single-file-upload-component-by-jquery-file-upload jQuery File Upload是一個非常優秀的上傳組件,主要使用了XHR作為上傳方式,并且利用了相當多的現代瀏覽器功能,所以可以實現諸如批量上傳、超…

vb趣味編程彈球小游戲_最好玩的微信小游戲集合,總有一款是你沒玩過的

大家好,這里是小雅龍生活趣味時間,自從17年微信推出小游戲程序以來,微信小游戲行業可謂是炙手可熱,知道2019年不斷有許許多多的微信小游戲如雨后春筍般的生根發芽。下面就由我帶大家來看看今年最好玩,最受歡迎的微信小…

開發MOSS2007 Masterpage的一些經驗

一直在做MOSS平臺的Masterpage開發,碰到很多的問題,總結了一些經驗,特此記錄: masterpage的所有的ContentPlaceholder詳細解釋見以下網址:http://www.cnblogs.com/WinYoung/archive/2007/06/25/791766.html 1.如果應用masterpage以后IE狀態欄出現""網頁指令碼錯誤訊息…

Golang——垃圾回收GC(2)

1 垃圾回收中的重要概念 1.1 定義 In computer science, garbage collection (GC) is a form of automatic memory management. The garbage collector, or just collector, attempts to reclaim garbage, or memory occupied by objects that are no longer in use by the pro…

java gui框架_推薦!程序員整理的Java資源大全

構建這里搜集了用來構建應用程序的工具。Apache Maven:Maven使用聲明進行構建并進行依賴管理,偏向于使用約定而不是配置進行構建。Maven優于Apache Ant。后者采用了一種過程化的方式進行配置,所以維護起來相當困難。Gradle:Gradle…

帆軟報表(finereport)控件背景色更改

setTimeout(function() {$(.fr-trigger-btn-up).css({"background-color": "#003399" });}, 100); 轉載于:https://www.cnblogs.com/Williamls/p/11571586.html

開心網分析,師從“中國緣”

作者:麥田   一,師從“中國緣” 開心網從08年“爆發”之后,網上出現很多評論文章。幾乎100%的評論文章都談到了開心網“不可思議”的爆發增長速度,比如幾個月就進入了alexa前500等等。但是,幾乎沒有一篇文章提到“開心…

HTML5+CSS3+JQuery1.9 輸入框切換和Div失焦模擬

Div失焦原理&#xff1a;判斷document點擊對象是否在Div容器以內&#xff0c;否則觸發事件 需要腳本&#xff1a;jquery-1.9.1.js 下載地址&#xff1a;http://download.csdn.net/detail/dmtnewtons/5807757 <!DOCTYPE> <html> <head> <meta http-equ…

資本冬天已至,開發者卻可以著眼未來

云&#xff0c;在國內外都已成為軟件開發者的首選服務。縱觀歷史&#xff0c;在云計算發展的這些年里&#xff0c;不管云上做了多少產品和服務&#xff0c;其實都離不開云最本質的價值體系&#xff1a;自服務、高彈性、按需提供、免運維&#xff0c;這些特性也讓云服務天然成為…

mybatis 大于_酸爽!IDEA 中這么玩 MyBatis,讓編碼速度飛起!

作者&#xff1a;Orsoncnblogs.com/java-class/p/6237564.html1. 搭建 MyBatis Generator 插件環境a. 添加插件依賴 pom.xmlb. 配置文件 generatorConfig.xmlc. 數據庫配置文件 jdbc.propertiesd. 配置插件啟動項2.項目實戰a. 比如在一個項目 我們要刪除某個小組下某個用戶的信…

Java的三種代理模式完整源碼分析

Java的三種代理模式&完整源碼分析 Java的三種代理模式&完整源碼分析 參考資料&#xff1a; 博客園-Java的三種代理模式 簡書-JDK動態代理-超詳細源碼分析 [博客園-WeakCache緩存的實現機制](https://www.cnblogs.com/liuyun1995/p/8144676.html) 靜態代理 靜態代理在使…

scatter函數_matplotlib.pyplot常用函數scatter講解大全(三)

前言這篇文章再來總結一個常用畫圖函數scatter-散點圖。參數常用參數示例import matplotlib.pyplot as plt import numpy as np#導入需要的包 datanp.random.multivariate_normal([0,1],[[1,0],[0,1]],200)#準備數據&#xff0c;二維正態分布plt.rcParams["axes.unicode_m…

如何徹底卸載MySQL

本文摘自&#xff1a;http://www.heiqu.com/show-64764-1.html 內容為&#xff1a; 由于安裝MySQL的時候&#xff0c;疏忽沒有選擇底層編碼方式&#xff0c;采用默認的ASCII的編碼格式&#xff0c;于是接二連三的中文轉換問題隨之而來&#xff0c;就想卸載了重新安裝MYSQL&…

vue-cli項目模板的一些思考

之前有個想法&#xff0c;就是要利用vue寫一套ui。然后當時也沒有搞清楚到底怎么寫。 幾經周轉吧&#xff0c;通過付費的方式在gitbook上面找到了答案。 找到答案之后再看我們正在開發的項目&#xff0c;看伙伴寫的代碼&#xff0c;突然發現完全可以按照寫ui組件庫的方式調整目…

flex基于svn協同開發

想做一個游戲&#xff0c;正好有人陪我做。于是想到用flex來協同開發。本來是想使用cvs&#xff0c;可是結果搗鼓了半天&#xff0c;也沒個結果——估計是最近沒怎么看電影&#xff0c;IQ降下來了。于是改用svn。 參考資料&#xff1a;http://www.flashmagazine.com/tutorials/…

cookie與session詳解

session與cookie是什么?session與cookie屬于一種會話控制技術.常用在身份識別&#xff0c;登錄驗證&#xff0c;數據傳輸等.舉個例子&#xff0c;就像我們去超市買東西結賬的時候&#xff0c;我們要拿出我們的會員卡才會獲取優惠.這時候&#xff0c;我們怎么識別這個會員卡真實…

c++萬能頭文件_初學Python,與C對比

?背景學了一學年的C的基礎&#xff0c;下學年開課Python&#xff0c;現在正在自學中...C也不是不學了&#xff0c;而是之前買了一本《CPrimer》在學校里&#xff0c;就準備先學一下Python&#xff0c;下學期利用自由時間接著學習C。這里分析了一下二者的優缺點&#xff0c;供大…