太好了!總算有人把動態代理、CGlib、AOP都說清楚了

Spring中AOP的兩種代理方式(Java動態代理和CGLIB代理)

Java 代理模式實現方式,主要有如下五種方法

  • 靜態代理,工程師編輯代理類代碼,實現代理模式;在編譯期就生成了代理類。
  • 基於 JDK 實現動態代理,通過jdk提供的工具方法Proxy.newProxyInstance動態構建全新的代理類(繼承Proxy類,並持有InvocationHandler接口引用 )字節碼文件並實例化對象返回。(jdk動態代理是由java內部的反射機制來實例化代理對象,並代理的調用委託類方法)
  • 基於CGlib 動態代理模式 基於繼承被代理類生成代理子類,不用實現接口。只需要被代理類是非final 類即可。(cglib動態代理底層是藉助asm字節碼技術
  • 基於 Aspectj 實現動態代理(修改目標類的字節,織入代理的字節,在程序編譯的時候 插入動態代理的字節碼,不會生成全新的Class )
  • 基於 instrumentation 實現動態代理(修改目標類的字節碼、類裝載的時候動態攔截去修改,基於javaagent) -javaagent:spring-instrument-4.3.8.RELEASE.jar (類裝載的時候 插入動態代理的字節碼,不會生成全新的Class )
太好了!總算有人把動態代理、CGlib、AOP都說清楚了

Notes

  • 委託類 即指的是代理模式中的被代理對象
  • 代理類 指的是生成的代表委託類的一個角色

靜態代理實現

靜態代理是代理類在編譯期間就創建好了,不是編譯器生成的代理類,而是手動創建的類。在編譯時就已經將接口,被代理類,代理類等確定下來。,軟件設計中所指的代理一般是指靜態代理,也就是在代碼中顯式指定的代理。

實現步驟

  • 委託類和代理類之間的約束接口Cat
  • 約束接口實現類 Lion,實現 Cat 接口,委託角色
  • 代理類實現 FeederProxy,實現Cat 接口,並含有一個 Cat接口引用屬性。代理角色,代理 cat接口屬性引用實例的行為並可以新增公共邏輯

Cat接口

委託類 Lion

代理類角色(FeederProxy)

靜態代理類測試

靜態代理很好的詮釋了代理設計模式,代理模式最主要的就是有一個公共接口(Cat),一個委託類(Lion),一個代理類(FeederProxy),代理類持有委託類的實例,代為執行具體類實例方法。上面說到,代理模式就是在訪問實際對象時引入一定程度的間接性,因為這種間接性,可以附加多種用途。這裡的間接性就是指客戶端不直接調用實際對象的方法,客戶端依賴公共接口並使用代理類。那麼我們在代理過程中就可以加上一些其他用途。就這個例子來說在 eatFood方法調用中,代理類在調用具體實現類之前添加System.out.println("proxy Lion exec eatFood ");語句 就是添加間接性帶來的收益。代理類存在的意義是為了增加一些公共的邏輯代碼。

動態代理類(基於接口實現)

靜態代理是代理類在代碼運行前已經創建好,並生成class文件;動態代理類 是代理類在程序運行時創建的代理模式。

動態代理類的代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。想想你有100個靜態代理類,現在有一個需求,每個代理類都需要新增一個處理邏輯,你需要打開100個代理類在每個代理方法裡面新增處理邏輯嗎?有或者代理類有5個方法,每個方法都需要新增一個處理邏輯, 你需要在每個方法都手動新增處理邏輯嗎?想想就挺無趣的。動態代理類幫你一鍵搞定。

動態代理類涉及角色

  • 委託類和代理類實現的公共接口(Person.java)
  • 實現公共接口的具體委託類(SoftwareEngineer.java)
  • InvocationHandler接口被Proxy類回調處理,一般實現 InvocationHandler 接口的類具有委託類引用,接口方法 invoke 中添加公共代碼並調用委託類的接口方法。(PersonInvocationHandler.java)
  • JDK提供生成動態代理類的核心類Proxy ( JDK 提供的Proxy.java)

基於JDK技術 動態代理類技術核心 Proxy類和一個 InvocationHandler 接口

java的java.lang.reflect包下提供了Proxy類和一個 InvocationHandler 接口,這個類Proxy定義了生成JDK動態代理類的方法 getProxyClass(ClassLoader loader,Class>... interfaces)生成動態代理類,返回class實例代表一個class文件。可以保存該 class 文件查看jdk生成的代理類文件長什麼樣

該生成的動態代理類繼承Proxy類,(重要特性) ,並實現公共接口。

InvocationHandler這個接口 是被動態代理類回調的接口,我們所有需要增加的針對委託類的統一處理邏輯都增加到invoke 方法裡面在調用委託類接口方法之前或之後 結束戰鬥。

案例

公共接口

具體實現類,等下被委託,被代理的類 SoftwareEngineer.java

InvocationHandler 接口實現 PersonInvocationHandler.java

PersonInvocationHandler invoke 方法中添加的公共代碼,這裡簡單以統計方法執行時間為邏輯

最後的是 怎麼創建代理類

解析JDK生成的動態代理類

saveGeneratedJdkProxyFiles方法 打開了存儲jdk生成的動態代理類 以 接口方法 goWorking 為例講解

Jdk為我們的生成了一個叫$Proxy0(這個名字後面的0是編號,有多個代理類會一次遞增)的代理類,這個類文件時默認不會保存在文件,放在內存中的,我們在創建代理對象時,就是通過反射獲得這個類的構造方法,然後創建代理對象實例。通過對這個生成的代理類源碼的查看,我們很容易能看出,動態代理實現的具體過程。

我們可以對 InvocationHandler 看做一箇中介類,中介類持有一個被代理對象,被Proxy類回調。在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用,把客戶端對invoke的調用最終都轉為對被代理對象的調用。

客戶端代碼通過代理類引用調用接口方法時,通過代理類關聯的中介類對象引用來調用中介類對象的invoke方法,從而達到代理執行被代理對象的方法。也就是說,動態代理Proxy類提供了模板實現,對外提供擴展點,外部通過實現InvocationHandler接口將被代理類納入JDK代理類Proxy。

太好了!總算有人把動態代理、CGlib、AOP都說清楚了

一個典型的基於JDK動態代理創建對象過程可分為以下四個步驟:

1、通過實現InvocationHandler接口創建自己的調用處理器 IvocationHandler handler = new InvocationHandlerImpl(...);

2、通過為Proxy類指定ClassLoader對象和一組interface代理類需要實現的接口,創建動態代理類類文件,默認JDK並不會保存這個文件到文件中;可以保存起來觀察生成的代理類結構Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

3、通過上面新建的代理clazz的反射機制獲取動態代理類的一個構造函數,其構造函數入參類型是調用處理器接口(IvocationHandler)類型 Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

4、通過構造函數實例創建代理類實例,此時需將調用處理器對象作為參數被傳入 Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler)); 為了簡化對象創建過程,Proxy類中的newInstance工具方法封裝了2~4,只需兩步即可完成代理對象的創建。

JDK動態代理特點總結

  • 生成的代理類:$Proxy0 extends Proxy implements Person,我們看到代理類繼承了Proxy類,Java的繼承機制決定了JDK動態代理類們無法實現對 類 的動態代理。所以也就決定了java動態代理只能對接口進行代理,
  • 每個生成的動態代理實例都會關聯一個調用處理器對象,可以通過 Proxy 提供的靜態方法 getInvocationHandler 去獲得代理類實例的調用處理器對象。在代理類實例上調用其代理的接口中所聲明的方法時,這些方法最終都會由調用處理器的 invoke 方法執行
  • 代理類的根類 java.lang.Object 中有三個方法也同樣會被分派到調用處理器的 invoke 方法執行,它們是 hashCode,equals 和 toString,可能的原因有:一是因為這些方法為 public 且非 final 類型,能夠被代理類覆蓋;二是因為這些方法往往呈現出一個類的某種特徵屬性,具有一定的區分度,所以為了保證代理類與委託類對外的一致性,這三個方法也應該被調用處理器分派到委託類執行。

JDK動態代理不足

JDK動態代理的代理類字節碼在創建時,需要實現業務實現類所實現的接口作為參數。如果業務實現類是沒有實現接口而是直接定義業務方法的話,就無法使用JDK動態代理了。(JDK動態代理重要特點是代理接口) 並且,如果業務實現類中新增了接口中沒有的方法,這些方法是無法被代理的(因為無法被調用)。

動態代理只能對接口產生代理,不能對類產生代理

基於CGlib 技術動態代理代理類實現 (基於繼承)

Cglib是針對類來實現代理的,他的原理是對代理的目標類生成一個子類,並覆蓋其中方法實現增強,因為底層是基於創建被代理類的一個子類,所以它避免了JDK動態代理類的缺陷。

但因為採用的是繼承,所以不能對final修飾的類進行代理。final修飾的類不可繼承。

導入maven 依賴

cglib 是基於asm 字節修改技術。導入 cglib 會間接導入 asm, ant, ant-launcher 三個jar 包。

業務類實現

cglib是針對類來實現代理的,原理是對指定的業務類生成他的一個子類,並覆蓋其中的業務方法來實現代理。因為採用的是繼承,所以不能對final修飾的類進行代理。

方法攔截器 實現 MethodInterceptor 接口

一個切面,用於在方法攔截器中intercept 方法中調用真正業務方法之前 之後處理邏輯

Cglib測試類

Cglib 總結

  • CGlib可以傳入接口也可以傳入普通的類,接口使用實現的方式,普通類使用會使用繼承的方式生成代理類.
  • 由於是繼承方式,如果是 static方法,private方法,final方法等描述的方法是不能被代理的
  • 做了方法訪問優化,使用建立方法索引的方式避免了傳統JDK動態代理需要通過Method方法反射調用.
  • 提供callback 和filter設計,可以靈活地給不同的方法綁定不同的callback。編碼更方便靈活。
  • CGLIB會默認代理Object中equals,toString,hashCode,clone等方法。比JDK代理多了clone。
太好了!總算有人把動態代理、CGlib、AOP都說清楚了

靜態代理 基於JDK動態代理 基於Cglib 動態代理

靜態代理是通過在代碼中顯式編碼定義一個業務實現類的代理類,在代理類中對同名的業務方法進行包裝,用戶通過代理類調用被包裝過的業務方法;

JDK動態代理是通過接口中的方法名,在動態生成的代理類中調用業務實現類的同名方法;

CGlib動態代理是通過繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行代理;

靜態代理在編譯時產生class字節碼文件,可以直接使用,效率高。動態代理必須實現InvocationHandler接口,通過invoke調用被委託類接口方法是通過反射方式,比較消耗系統性能,但可以減少代理類的數量,使用更靈活。cglib代理無需實現接口,通過生成類字節碼實現代理,比反射稍快,不存在性能問題,但cglib會繼承目標對象,需要重寫方法,所以目標對象不能為final類。

AOP 實現案例

AOP的源碼中用到了兩種動態代理來實現攔截切入功能:jdk動態代理和cglib動態代理。兩種方法同時存在,各有優劣。jdk動態代理是由java內部的反射機制來實現的,cglib動態代理底層則是藉助asm來實現的。總的來說,反射機制在生成類的過程中比較高效,執行時候通過反射調用委託類接口方法比較慢;而asm在生成類之後的相關代理類執行過程中比較高效(可以通過將asm生成的類進行緩存,這樣解決asm生成類過程低效問題)。還有一點必須注意:jdk動態代理的應用前提,必須是委託類基於統一的接口。如果沒有上述前提,jdk動態代理不能應用。由此可以看出,jdk動態代理有一定的侷限性,cglib這種第三方類庫實現的動態代理應用更加廣泛,且在效率上更有優勢。

實現AOP關鍵特點是定義好兩個角色 切點 和 切面 。 代理模式中被代理類 委託類處於切點角色,需要添加的其他比如 校驗邏輯,事務,審計邏輯 屬於非功能實現邏輯通過 切面類定義的方法插入進去。

JDK動態代理 aop 實現方式

定義切面接口,完成將通用公共方法注入到被代理類接口調用處理中

定義切面實現類

定義切點角色接口 因為是基於JDK實現的Aop ,所以委託類需要基於接口實現。

委託類實現

JDK動態代理生成器工具類

可以看到 generatorJDKProxy 方法入參只有兩個參數 一個切點接口引用,一個切面接口引用;在InvocationHandler 內部類中可以完整看到切面類方法是怎麼影響切點代碼執行邏輯的。

測試類

Cglib aop 實現方式

定義切面接口

切面實現

Cglib 是基於類實現的動態代理即業務類只需要實現類即可,不用強制必須實現某個接口為了突出這個優點這裡沒有實現接口

Cglib 動態代理生成器工具類

測試類

AspectJ 實現 AOP 效果

AOP 實現的關鍵就在於 AOP 框架自動創建的 AOP 代理,AOP 代理則可分為靜態代理和動態代理兩大類:

  • 靜態代理是指使用 AOP 框架提供的命令進行編譯,從而在編譯階段通過AOP框架指令生成 AOP 代理類,因此也稱為編譯時增強;還有一種靜態代理是編寫代碼實現不用工具;這種方式一般是代理模式會使用。
  • 動態代理則在運行時藉助於 JDK 動態代理、CGLIB 等在內存中“臨時”生成 AOP 動態代理類,因此也被稱為運行時增強。

基於 AspectJ 的編譯時增強進行 AOP POM 依賴

原生 AspectJ 不依賴Spring案例, 基於 AspectJ 的編譯時增強進行 AOP 它是在編譯期修改字節碼,增強功能;並不會生成新的代理類字節碼。

動態代理 使用場景

  • 日誌集中打印
  • 事務
  • 權限管理
  • AOP

【1】想領取java相關知識可以關注我下方評論轉發後,私信“資料”。

【2】部分資料有時間限制,抓緊時間吧~、

感謝大家支持!



分享到:


相關文章: