在上篇 如何實現 AOP(上) 介紹了 AOP
技術出現的緣由和一些重要的概念,在咱們本身實現以前有必要先了解一下 AOP
底層究竟是如何運做的,因此這篇再來看看 AOP
實現所依賴的一些核心基礎技術。AOP
是使用動態代理
和字節碼生成技術
來實現的,在運行期(注意:不是編譯期!)爲目標對象生成代理對象,而後將橫切邏輯織入到生成的代理對象中,最後系統使用的是帶有橫切邏輯的代理對象,而不是被代理對象,由代理對象轉發到被代理對象。html
動態代理的根源是設計模式中的代理模式,代理模式在 GoF 中的描述以下:java
Provide a surrogate or placeholder for another object to control access to it.
從其定義能夠看出,代理模式主要是爲了控制對象的訪問,一般也會擁有被代理者的全部功能。經過代理模式咱們能夠在不改變被代理類的狀況下,經過引入代理類來給被代理類添加一些功能,此時腦海裏飄過計算機科學界中一句著名的話(P.S. 基礎知識很重要啊,能夠參見 這篇):git
計算機科學的任何一個問題,均可以經過增長一箇中間層來解決。
代理模式其實在現實生活中也常常會接觸到,好比在一線城市租房時大部分都是找的租房中介去看的房子、談價格以及籤合同,是由於房子的房東已經把房子全權託管給了中介處理了,這裏的租房中介其實就是充當了代理模式中的代理對象的解決,而真正的被代理對象(目標對象)實際上是房子的房東,而和咱們打交道都是租房中介(代理對象)。代理模式類圖結構以下圖所示:github
圖中各個部分的含義以下:編程
ISubject
接口的實例。ISubject
類型的資源。能夠看到 SubjectImpl
和 SubjectProxy
都實現了相同的接口 ISubject
,在代理對象 SubjectProxy
內持有 ISubject
的引用,當 Client
訪問 doOperation()
時,代理對象將請求轉發給被代理對象,單單從這個過程來看,代理對象若是隻是爲了轉發請求,是否是有點畫蛇添足了?再結合代理模式的定義思考一下,在轉發以前(後者以後)不就能夠添加一些訪問控制了嗎。設計模式
在代理對象(SubjectProxy
)將請求轉發給被代理對象(SubejctImpl
)以前或者以後都是根據須要添加一些處理邏輯,而不須要修改被代理對象的具體實現邏輯,假設 SubjectImpl
是咱們系統中 Joinpoint
所在的對象,此時 SubjectImpl
就是咱們的目標對象了,只須要爲這個目標對象建立一個代理對象,而後將橫切邏輯添加到代理對象中,對外暴露出建立出來的代理對象就能夠將將橫切邏輯和原來的邏輯融合在一塊兒了。ide
到目前爲止,一切都是那麼美好,當只爲同一個目標對象類型添加橫切邏輯時,只須要建立一個代理對象便可,可是在 Joinpoint
相同而目標對象類型不一樣時,須要爲每一個不一樣的目標對象類型都單首創建一個代理對象,而這些代理對象的橫切邏輯其實都是同樣的,根據 DRY原則,須要尋找另外一種技術來解決這個問題。
在 JDK 1.3
引入的 動態代理機制
能夠爲指定的接口在運行期
動態的去生成代理對象,使用這個動態代理機制
能夠解決上述問題,這樣咱們就能夠不事先爲每一個原始類建立代理類,而是在運行時動態生成
代理類。在 Java
中,使用動態代理是比較簡單的,它自己就已經使用反射實現了動態代理的語法,主要是由一個類 Proxy
和一個接口 InvocationHandler
組成,使用動態代理機制實現前文示例以下:工具
/** * @author mghio * @since 2021-05-29 */ public interface Subject { void doOperation(); } public class SubjectImpl implements Subject { @Override public void doOperation() { System.out.println("SubjectImpl doOperation..."); } } public class JDKInvocationHandler implements InvocationHandler { private final Subject target; public JDKInvocationHandler(Subject target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // add pre process logic if necessary System.out.println("Proxy before JDKInvocationHandler doOperation..."); Object result = method.invoke(target, args); System.out.println("Proxy after JDKInvocationHandler doOperation..."); // add post process logic if necessary return result; } } public class Client { public static void main(String[] args) { // 將生成的代理類保存在根目錄下(com/sun/proxy/XXX.class) System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Subject target = new SubjectImpl(); Subject proxy = (Subject) Proxy.newProxyInstance(SubjectImpl.class.getClassLoader(), new Class[]{Subject.class}, new JDKInvocationHandler(target)); proxy.doOperation(); } }
由以上代碼可知,使用 JDK
的動態代理只要 3 步:post
Subject
,建立被代理對象 SubjectImpl
JDKInvocationHandler
,持有目標對象 Subject
的引用JDK
的 Proxy
類的靜態方法 newProxyInstance
建立代理對象經過設置 sun.misc.ProxyGenerator.saveGeneratedFiles
屬性,能夠將動態生成的代理類保存在項目根目錄下,運行上面的示例代碼生成的代理類以下:this
public final class $Proxy0 extends Proxy implements Subject { private static Method m1; private static Method m2; private static Method m3; private static Method m0; static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("cn.mghio.designpattern.proxy.dynamicproxy.Subject").getMethod("doOperation"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } public $Proxy0(InvocationHandler var1) throws { super(var1); } public final void doOperation() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } // omit toString、equals、hashCode method... }
從動態生成的代理類的代碼能夠看出,JDK
動態代理生成的代理類是繼承 JDK
中提供的 Proxy
和實現被代理類所實現的接口。進一步能夠從這個實現方式得出兩點:1. 使用 JDK
動態代理時爲何只能使用接口引用指向代理
,而不能使用被代理的具體類引用指向代理;2. 被代理類必須實現接口,由於 JDK
動態代理生成的代理類必須繼承自 Proxy
,而 Java
不支持多重繼承,因此只能經過實現接口的方式。
在默認狀況下,Spring AOP
發現了目標對象實現了接口,會使用 JDK
動態代理機制爲其動態生成代理對象,雖然提倡面向接口編程
,可是也有目標對象沒有實現接口的場景,當被代理的目標對象沒有實現接口時就沒法使用 JDK
動態代理了,那麼這種狀況下就須要使用第三方工具來幫忙了。
當目標對象沒有實現接口時,能夠經過動態字節碼生成來繼承目標對象來動態生成相應的子類,在生成的子類中重寫
父類目標對象的行爲,而後將橫切邏輯放在子類,在系統中使用目標對象的子類,最終的效果是代理模式是同樣的,CGLIB 動態字節碼生成類庫(它自己其實也是一個抽象層,更底層是 ASM)能夠動態生成和修改一個類的字節碼。當以上示例代碼目標對象未實現接口是修改成 CGLIB
動態生成字節碼方式實現以下:
/** * @author mghio * @since 2021-05-29 */ public class RealSubject { public void doOperation() { System.out.println("RealSubject doOperation..."); } } public class CglibMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // add pre process logic if necessary System.out.println("Cglib before RealSubject doOperation..."); Object result = methodProxy.invokeSuper(o, args); System.out.println("Cglib after RealSubject doOperation..."); // add post process logic if necessary return result; } } public class Client { public static void main(String[] args) { // 將生成的動態代理類保存到文件中 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/mghio/IdeaProjects/designpattern/cglib/proxy/"); // 1. 建立 Enhancer 對象 Enhancer enhancer = new Enhancer(); // 2. 設置被代理類 enhancer.setSuperclass(RealSubject.class); // 3. 設置回調對象(實現 MethodInterceptor 接口) enhancer.setCallback(new CglibMethodInterceptor()); // 4. 建立代理對象 RealSubject proxy = (RealSubject) enhancer.create(); proxy.doOperation(); } }
使用 CGLIB
生成代理對象須要 4 步:
Enhancer
對象,動態生成字節碼的絕大部分邏輯都是在這個類中完成的。final
類型的。MethodInterceptor
接口),在這裏根據須要添加橫切邏輯。Enhaner
的 create()
方法建立代理對象。在設置 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY
屬性後,反編譯已保存的動態生成代理類以下:
從反編譯後的代碼能夠看出 CGLIB
生成的代理類是經過繼承被代理類 RealSubject
實現 Factory
接口實現的,要能被繼承也就要求被代理類不能是 final
類型的。看到這裏你可能會問:既然 JDK
動態代理要求被代理類實現接口,而 CGLIB
動態字節碼生成要求不能是 final
類,那對於那些沒有實現接口同時仍是 final
類,要怎麼動態代理呢?好問題,這個就留給你本身去思考了。
本文簡要介紹了 Spring AOP
實現所依賴的核心基礎技術,從動態代理的根源代理模式到動態代理和動態字節碼生成技術,爲下篇動手實現簡易版的 AOP
打下基礎,在瞭解了所依賴的基礎技術後,在具體實現時就會更加絲滑,動態代理
和動態字節碼生成
對好比下:
對比項 | JDK 動態代理 | CGLIB |
---|---|---|
生成代理類的方式 | 繼承 JDK 中 Proxy ,實現被代理類的全部接口 |
繼承被代理類,實現 CGLIB 的 Factory 接口 |
被代理對象的要求 | 必須實現接口,能夠是 final 類 |
非 final 類,方法也要是非 final 類型的 |
集成方式 | JDK 內置 |
第三方動態字節碼生成類庫 |