基于JDK動態(tài)代理原理解析
1、回顧一下JDK動態(tài)代理的核心參數(shù)
如果我們要為target類創(chuàng)建一個【JDK動態(tài)代理對象】,那么我們必須要傳入如下三個核心參數(shù)
- 加載target類的類加載器
- target類實現(xiàn)的接口
- InvocationHandler
為什么必須要這三個參數(shù)呢?之前使用動態(tài)代理的時候都是直接按接口要求傳這三個參數(shù),但從來沒想過為什么?下面仔細去探究一下
2、實現(xiàn)一個簡單的動態(tài)代理
【JDK動態(tài)代理】的核心其實是借助【Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)】方法,去創(chuàng)建的動態(tài)代理對象,我們這里也使用這個方法去創(chuàng)建一個簡單的【動態(tài)代理對象】以便于理解他的核心原理。
①、定義接口Subject
public interface Subject { /** * 接口方法 */ void doSomething(); /** * sayHello * * @param name name * @return string */ String sayHello(String name); }
②、定義接口的實現(xiàn)類RealSubject
實現(xiàn)Subject接口,并實現(xiàn)接口相關(guān)方法
public class RealSubject implements Subject { @Override public void doSomething() { System.out.println("RealSubject do something"); } @Override public String sayHello(String name) { System.out.println("RealSubject sayHello"); return "hello-" + name; } }
③、定義代理對象創(chuàng)建工廠
- 定義一個工廠類,該工廠類用于為target對象生產(chǎn)代理對象
- 該工廠類借助
Proxy.newProxyInstance
來為目標對象創(chuàng)建代理對象
public class JdkDynamicProxyFactory { /** * 創(chuàng)建target類的代理對象 * 注意:當(dāng)調(diào)用代理對象中的方法時,其實就是調(diào)用的InvocationHandler里面的invoke方法,然后在invoke方法里調(diào)用目標對象對應(yīng)的方法 * * @param <T> 泛型 * @return 代理對象 */ public static <T> T getProxy(Object target) { // 創(chuàng)建代理實例,分別傳入:【加載target類的類加載器、target類實現(xiàn)的接口、InvocationHandler】 Object proxyInstance = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("執(zhí)行目標方法前"); // 執(zhí)行目標方法 Object result = method.invoke(target, args); System.out.println("執(zhí)行目標方法后"); // 返回目標方法的執(zhí)行結(jié)果 return result; } }); // 返回代理對象 return (T) proxyInstance; } }
④、創(chuàng)建測試類,為target創(chuàng)建代理對象
該測試類會將內(nèi)存中的動態(tài)代理對象保存到磁盤上,以便于我們后續(xù)分析生成的動態(tài)代理類的具體結(jié)構(gòu)
public class Client { public static void main(String[] args) { // 保存生成的代理類的字節(jié)碼文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 目標對象 RealSubject target = new RealSubject(); // 使用JDK動態(tài)代理為【target對象】創(chuàng)建代理對象 Subject proxy = JdkDynamicProxyFactory.getProxy(target); // 調(diào)用代理對象的方法 proxy.doSomething(); System.out.println("=====================華麗的分割線====================="); proxy.sayHello("wenpan"); } }
3、分析生成的動態(tài)代理類的結(jié)構(gòu)
在上面一步中我們使用
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
將動態(tài)生成的代理保存到了磁盤上,下面我們就具體看看生成的代理類長什么樣
- 可以看到代理類【繼承了Proxy類】,并且【實現(xiàn)了目標接口Subject】,覆寫了接口中的【每一個方法】
- 這也說明了為什么JDK代理需要實現(xiàn)接口,因為Java是單繼承的,既然代理類繼承了Proxy類那么就無法再繼承其他類了
- 在代理類中將我們的【目標接口Subject】的【所有方法(包括object父類的方法)】都以【靜態(tài)屬性的形式】保存了起來(主要是為了方便后面的【反射調(diào)用】)
- 在調(diào)用動態(tài)代理對象的某個方法時(比如:調(diào)用doSomething方法),實質(zhì)上是調(diào)用的【Proxy類】的【h屬性】的invoke方法
- 所以我們要重點去看看這個【Proxy.h】到底是個什么鬼?其實他就是創(chuàng)建代理對象是我們傳入的【InvocationHandler】
// 1、代理類首先繼承了Proxy類(這也說明了為什么JDK代理需要實現(xiàn)接口,因為Java是單繼承的),并且實現(xiàn)了目標接口Subject public final class $Proxy0 extends Proxy implements Subject { // 2、可以看到,在代理類中將我們的【目標接口Subject】的【所有方法(包括object父類的方法)】都以【靜態(tài)屬性的形式】保存了起來 private static Method m1; private static Method m3; private static Method m4; private static Method m2; private static Method m0; // 以靜態(tài)代碼塊的形式為屬性賦值 static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("doSomething"); m4 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("sayHello", Class.forName("java.lang.String")); m2 = Class.forName("java.lang.Object").getMethod("toString"); 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); } // 3、object父類的equals方法 public final boolean equals(Object var1) throws { try { // 這里的supper是指的Proxy類,調(diào)用【Proxy類】的【h屬性】的invoke方法執(zhí)行 // 重點:【注意這里的super.h】其實就是我們創(chuàng)建代理對象是傳入的【InvocationHandler】,不信往后面看 // 這里也就體現(xiàn)了創(chuàng)建代理對象時為什么需要傳入【InvocationHandler】,以及為什么調(diào)用代理對象的方法時都是執(zhí)行的invoke方法 return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } // 4、目標接口的方法 public final void doSomething() throws { try { // 調(diào)用了【Proxy.h屬性】的invoke方法 // 注意這里的super.h】其實就是我們創(chuàng)建代理對象是傳入的【InvocationHandler】,不信往后面看 super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } // 5、目標接口的方法 public final String sayHello(String var1) throws { try { // 調(diào)用了【Proxy.h屬性】的invoke方法 // 注意這里的super.h】其實就是我們創(chuàng)建代理對象是傳入的【InvocationHandler】,不信往后面看 return (String)super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } }
4、Proxy.h是什么鬼
在上面的第三點中我們看到,執(zhí)行代理對象的方法的時候其實質(zhì)是調(diào)用的【super.h.invoke方法】也就是【Proxy.h.invoke方法】,那么我們仔細看看【Proxy.h】到底是什么鬼
①、Proxy.newProxyInstance是如何創(chuàng)建代理對象的
下面的源代碼我做了一些刪減,只留下了最核心的部分
- 通過下面代碼我們就明確了使用【newProxyInstance】方法創(chuàng)建代理對象時所做的幾件事情
- 首先通過【目標接口】 + 【類加載器】創(chuàng)建一個Proxy類的【Class對象】
- 然后通過這個【Class對象】獲取到他的有參構(gòu)造器,并且傳入我們自定義的【InvocationHandler】作為構(gòu)造函數(shù)參數(shù),并且通過反射的方式調(diào)用有參構(gòu)造器創(chuàng)建一個【Proxy對象】
- 在【Proxy的有參構(gòu)造器】中,會將傳入的【InvocationHandler】保存到 【h屬性】上(方便后面的supper.h.invoke調(diào)用)
- 代理對象創(chuàng)建完畢
public class Proxy implements java.io.Serializable { // h屬性,保存我們傳遞進來的InvocationHandler protected InvocationHandler h; // 【有參構(gòu)造器】注意這里的參數(shù) protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } // 生成代理對象的方法 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{ // 1、InvocationHandler強制不允許為空 Objects.requireNonNull(h); // 獲取到目標接口 final Class<?>[] intfs = interfaces.clone(); /* * Look up or generate the designated proxy class. * 2、獲取到代理類的Class對象(也就是Proxy) */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. * 通過反射執(zhí)行 cl 的有參構(gòu)造,也就是下面這個,可以看到通過反射執(zhí)行Proxy有參構(gòu)造, * 將InvocationHandler賦值到了h屬性上 */ try { // 3、獲取到有參構(gòu)造器 final Constructor<?> cons = cl.getConstructor(constructorParams); // 4、通過構(gòu)造器來創(chuàng)建一個代理對象并返回,這里傳入的參數(shù)h 就是我們的【InvocationHandler】 return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { // 省略.... } } }
5、問題總結(jié)
現(xiàn)在再來看上面拋出的三個問題!
為什么創(chuàng)建代理對象時需要傳入如下三個參數(shù):
- 加載target類的類加載器
- target類實現(xiàn)的接口
- InvocationHandler
①、為什么要傳入類加載器
因為動態(tài)代理類也是類,我們普通的類是從【磁盤上的.class文件(也可以是其他地方,比如網(wǎng)絡(luò)上)】里加載而來,而動態(tài)代理類則是在【運行過程中動態(tài)生成的類】。
那么既然是類那么他就一定要【被類加載器加載后】才能被我們的【Java虛擬機】識別。
所以我們會傳入【加載target類的類加載器】,用該類加載器來加載【動態(tài)生成的代理類】
②、為什么要傳入target類實現(xiàn)的接口
為啥要傳入【target類實現(xiàn)的接口】呢?直接【繼承目標類】不行嗎?肯定不行
- 從上面的【動態(tài)生成的代理類的結(jié)構(gòu)】來看,代理類繼承了Proxy類,由于【Java是單繼承】的,所以無法再通過繼承的方式來繼承【目標類】了。
- 所以動態(tài)代理類需要【實現(xiàn)目標接口】,來重寫接口的方法
③、為什么要傳入InvocationHandler
- 從【動態(tài)生成的代理類的結(jié)構(gòu)】可以看出,我們傳入的
InvocationHandler
最終會被作為一個屬性保存到Proxy對象的【h屬性】上 - 并且【動態(tài)代理對象】會覆寫【目標接口的所有方法】,在方法中會使用
supper.h.invoke
的方式調(diào)用InvocationHandler的invoke方法,所以我們需要傳入InvocationHandler
- 動態(tài)代理的每個方法調(diào)用都會先走
InvocationHandler.invoke()
方法
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java可重入鎖的實現(xiàn)原理與應(yīng)用場景
今天小編就為大家分享一篇關(guān)于Java可重入鎖的實現(xiàn)原理與應(yīng)用場景,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01java數(shù)據(jù)結(jié)構(gòu)與算法之雙向循環(huán)隊列的數(shù)組實現(xiàn)方法
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)與算法之雙向循環(huán)隊列的數(shù)組實現(xiàn)方法,結(jié)合實例形式分析了雙向循環(huán)隊列的原理與數(shù)組實現(xiàn)技巧,并附帶說明了該算法的用途,需要的朋友可以參考下2016-08-08SpringBoot Shiro配置自定義密碼加密器代碼實例
這篇文章主要介紹了SpringBoot Shiro配置自定義密碼加密器代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03Java使用easyExcel導(dǎo)出數(shù)據(jù)及單元格多張圖片
除了平時簡單的數(shù)據(jù)導(dǎo)出需求外,我們也經(jīng)常會遇到一些有固定格式或者模板要求的數(shù)據(jù)導(dǎo)出,下面這篇文章主要給大家介紹了關(guān)于Java使用easyExcel導(dǎo)出數(shù)據(jù)及單元格多張圖片的相關(guān)資料,需要的朋友可以參考下2023-05-05MybatisPlus使用Mybatis的XML的動態(tài)SQL的功能實現(xiàn)多表查詢
本文主要介紹了MybatisPlus使用Mybatis的XML的動態(tài)SQL的功能實現(xiàn)多表查詢,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11合成聚合復(fù)用原則_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了合成聚合復(fù)用原則,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08