JDK動態(tài)代理原理:只能代理接口,不能代理類問題
動態(tài)代理是許多框架底層實現(xiàn)的基礎(chǔ),比如Spirng的AOP等,其實弄清楚了動態(tài)代理的實現(xiàn)原理,它就沒那么神奇了,下面就來通過案例和分析JDK底層源碼來揭秘她的神秘面紗,讓她呈現(xiàn)在我們面前。
一、代理模式定義
存在一個代理對象,并且這個代理對象持有真實對象的引用,以實現(xiàn)對真實對象的訪問控制。
1、舉個例子,現(xiàn)在公司一般都有VPN,使我們在家也能訪問到公司的內(nèi)網(wǎng)(比如連接公司的數(shù)據(jù)庫等),實現(xiàn)居家辦公。這里VPN相當(dāng)于一個代理,而公司內(nèi)網(wǎng)相當(dāng)于被代理對象,也就是真實對象。
我們不能直接訪問公司內(nèi)網(wǎng)(真實對象),但是我們通過VPN(代理對象),輸入身份信息,確認(rèn)無誤后就可以訪問到公司內(nèi)網(wǎng)。
這就是代理模式的作用----控制對象的訪問。
代理模式的分類
1、靜態(tài)代理
該代理對象持有被代理對象的引用,客戶端通過調(diào)用代理對象的方法間接實現(xiàn)調(diào)用真實對象的方法。
代理對象可以在調(diào)用真實對象的方法前面加入一些操作:比如身份驗證,如果身份驗證沒有通過,則不能訪問真實對象的方法,否則可以調(diào)用真實對象的方法;也可以在調(diào)用真實對象方法后,加入一些操作,比如記錄訪問日志。
真實對象接口,提供兩個服務(wù)方法
/** ?* ?* People 真實對象的接口包含兩個方法 ?* ?*/ public interface People { ? ? voidsayHello(String msg); ? ? ? voidsayBye(String msg); } ? 真實對象接口的具體實現(xiàn) ? /** ?* ?*Student ?* 真實對象接口的實現(xiàn) ?*/ public class Student implements People { ? ? ? @Override ? ? public void sayHello(String msg) { ? ? ? System.out.println("Hello "+msg); ? ? } ? ? ? @Override ? ? public void sayBye(String msg) { ? ? ? ? System.out.println("ByeBye "+msg); ? ? }? }
代理對象:
/** ?* ?*StaticProxy ?* 代理對象,控制對真實對象的訪問控制 ?*/ public class StaticProxy implements People { ? ? //真實對象,客戶端不能直接訪問 ? ? private Peoplepeople; ? ? ? ? publicStaticProxy(){ ? ? ? ? this.people=new Student(); ? ? } ? ? ? @Override ? ? public void sayHello(String msg) { ? ? ? ? booleanfriendFlag=true; ? ? ? ? if(friendFlag){ ? ? ? ? ? ? people.sayHello(msg); ? ? ? ? } ? ? ? ? System.out.println("記錄訪問日志"); ? ? ?? ? ? } ? ? ? @Override ? ? public void sayBye(String msg) { ? ? ? ? booleanfriendFlag=true; ? ? ? ? if(friendFlag){ ? ? ? ? ? ? people.sayBye(msg); ? ? ? ? } ? ? ? ? System.out.println("記錄訪問日志"); ? ? } }
客戶端調(diào)用及結(jié)果:
public class Test_Demo { ?? ?public static void main(String[] args) { ? ? ? ? //創(chuàng)建靜態(tài)代理對象 ?? ??? ?StaticProxy proxy = new StaticProxy(); ? ? ? ? //調(diào)用靜態(tài)代理對象方法 ? ? ? ? proxy.sayHello("nihao"); ? ? ? ? proxy.sayBye("zaijian"); ? ? ?? ?? ?} }
輸出結(jié)果為:
nihao
記錄訪問日志
zaijian
記錄訪問日志
上面就是靜態(tài)代理的一個實現(xiàn),通過靜態(tài)代理,實現(xiàn)了訪問控制,但是在每個真實對象方法之前都加入了訪問控制代碼來驗證權(quán)限。
如果有很多個方法,則要在每個方法調(diào)用前都加入驗證權(quán)限的代碼,這樣非常的不靈活且有大量的重復(fù)代碼,即使把驗證權(quán)限抽象出來做過方法或者類,但是還是得在每個方法前加一段調(diào)用權(quán)限驗證的代碼,比如,一個客戶端只用其中的一個方法,但是代理中兩個方法都要加入權(quán)限控制,要滿足其他客戶端的調(diào)用需求,上面接口中只有兩個方法還好,但是如果有上百個方法那豈不是很臃腫。
那么有什么辦法解決了,那就是動態(tài)代理。
2、動態(tài)代理
動態(tài)的生成代理類,而不用像靜態(tài)代理一樣,在編譯期間進(jìn)行定義類。動態(tài)代理更加靈活,不用顯示的在所有方法前面或者后面加入權(quán)限驗證、記錄日志等操作。
動態(tài)代理的實現(xiàn)和靜態(tài)代理一樣,不同的是代理類的創(chuàng)建方式不同:
- (1)靜態(tài)代理是直接新增一個代理類;
- (2)動態(tài)代理是通過JDK的Proxy和一個調(diào)用處理器InvocationHandler來實現(xiàn)的,通過Proxy來生成代理類實例,而這個代理實例通過調(diào)用處理器InvocationHandler接收不同的參數(shù)靈活調(diào)用真實對象的方法。
因此: 我們需要做的是創(chuàng)建調(diào)用處理器,該調(diào)用處理器必須實現(xiàn)JDK的InvocationHandler
1、 動態(tài)代理的實現(xiàn)如下:
(1)被代理接口:
public interface Car { ?? ?public void run(); ?? ?public void laba(String str); }
(2)被代理接口實現(xiàn)類:
public class BenChi implements Car { ?? ?@Override ?? ?public void run() { ?? ??? ?System.out.println("奔馳啟動快"); ?? ?} ?? ?@Override ?? ?public void laba(String str) { ?? ??? ?System.out.println("過路口要減速鳴笛"+str); ?? ?} }
(3)代理類:
public class P_Class implements InvocationHandler { ?? ?private Car car; ?? ?public P_Class(Car car){ ?? ??? ?this.car = car; ?? ?} ?? ?public Car createProxy(){ ?? ??? ?Car car_proxy = (Car) Proxy.newProxyInstance(car.getClass().getClassLoader(),? ?? ??? ??? ??? ?car.getClass().getInterfaces(), this); ?? ??? ?return car_proxy; ?? ?} ? ?? ?@Override ?? ?public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ?? ??? ?if("run".equals(method.getName())){ ?? ??? ??? ?System.out.println("開車不喝酒,喝酒不開車"); ?? ??? ??? ?method.invoke(car, args); ?? ??? ??? ?System.out.println("安全伴我行"); ?? ??? ??? ?System.out.println(proxy.getClass().getName());?? ??? ??? ? ?? ??? ?} ?? ??? ?return null; ?? ?} }
(4)測試類:
public class Test_Demo { ?? ?public static void main(String[] args) { ?? ??? ?Car benchi = new BenChi(); ?? ??? ?Car benchi_proxy = new P_Class(benchi).createProxy(); ?? ??? ?benchi_proxy.run(); ?? ??? ?benchi.laba("dididididi");?? ? ?? ?} }
輸出結(jié)果:
開車不喝酒,喝酒不開車
奔馳啟動快
安全伴我行
com.sun.proxy.$Proxy0
過路口要減速鳴笛dididididi
2、Proxy動態(tài)生成一個代理實例源碼分析:
/** ? ? ?*? ? ? ?* 通過Proxy動態(tài)生成一個代理實例 ? ? ?*return:Object ? ? ?*/ ? ? public Object getProxy(){ ? ? ? ? /* ? ? ? ? ?* Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 注意:loader類加載器用于加載代理類的字節(jié)碼;interfaces為代理類需要實現(xiàn)的接口;h為代理對象實際調(diào)用的方法即invoke方法。 ? ? ? ? ?* 第一個參數(shù)的作用就是 獲取當(dāng)前類的類加載器,作用是用來生成類的 ? ? ? ? ?* 第二個參數(shù)是獲取真實對象的所有接口 ? ?獲取所有接口的目的是用來生成代理的,因為代理要實現(xiàn)所有的接口 ? ? ? ? ?* 第三個參數(shù)是 調(diào)用處理器 ?這里傳入調(diào)用處理器,是因為生成代理實例需要 調(diào)用處理器 ? ?為什么需要調(diào)用處理器,因為生成的代理不能直接調(diào)用真實對象的方法,而是通過調(diào)用處理器來調(diào)用真實對象的方法,具體就是通過上面定義的P_Class重寫父類InvocationHandler的invoke方法 ? ? ? ? ?*/ ? ? ? ?return ?Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); ? ? } }
這樣則實現(xiàn)了動態(tài)代理,客戶端調(diào)用代理不同的方法,都實現(xiàn)了對真實對象的間接調(diào)用,并且經(jīng)過了代理對象的權(quán)限驗證。
但是我們只在一個地方加入了權(quán)限驗證的代碼,并沒有在每個方法前面都加入,這樣更加靈活和優(yōu)雅。
但是我們重頭到尾都沒有看到像靜態(tài)代理類那樣的一個動態(tài)代理類,那么JDK的Proxy是怎么得到動態(tài)代理類的實例的呢?真的不建立一個類,就能獲取該類的實例嗎?
二、JDK動態(tài)原理詳解
這是不可能的,Java中必須要有類,才會有該類的實例。其實不是沒有代理類,而是JDK在運行期間幫我們生成了一個代理類的字節(jié)碼,通過類加載器加載這個字節(jié)碼,然后執(zhí)行引擎進(jìn)行一系列處理后生成代理類,再進(jìn)行實例化。
下面就來看JDK是怎么生成代理類并且實例化的:
核心代碼就是:
Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this );
看看JDK的底層實現(xiàn)
貼出代碼,去掉了異常和判斷
public static Object newProxyInstance(ClassLoader loader, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Class<?>[] interfaces, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? InvocationHandler h) ? ? ? ? throws IllegalArgumentException ? ? { ? ? ?? ? ? ? ? ? ? ? Class<?> cl = getProxyClass(loader, interfaces); ? ? ? ? // Class[] constructorParams = ? ? ? ? ?{ InvocationHandler.class }; ? ? ? ? ? ? ? Constructor cons = cl.getConstructor(constructorParams); ? ? ? ? ? ? return cons.newInstance(new Object[] { h }); ? ? ? ? }?
1、生成代理類
通過源碼可以知道,生成代理類是通過如下方法實現(xiàn)的:
Class<?> cl =getProxyClass(loader, interfaces);
2、生成代理類字節(jié)碼
再追蹤一下,這個方法里面內(nèi)容很多,但是最關(guān)鍵的就是下面這個方法:
? ?byte[] proxyClassFile = ProxyGenerator.generateProxyClass( ? ? ? ? ? ? ? ? ? ? proxyName, interfaces);
通過這個方法就生成了代理類的字節(jié)碼,只不過調(diào)用完就不存在了。所以我們看不到它的源碼。
注意:生成的動態(tài)代理類其實跟靜態(tài)代理類還是有區(qū)別的,靜態(tài)代理是我們直接控制真實對象的方法調(diào)用,而動態(tài)代理是通過調(diào)用處理器的invoke方法來調(diào)用真實對象的方法,而這個invoke方法就是我們自己覆寫的方法。
可以看出是通過反射實現(xiàn)的,通過傳入的不同的方法對象和參數(shù)來調(diào)用真實對象的不同方法。
剛開始我和網(wǎng)上很多人一樣都有一個疑問,對于invoke方法發(fā)參數(shù),Method和args在我們覆寫的invoke方法中都有用到,但是對于第一個參數(shù),代理對象proxy沒有用,所以不知道這個東西調(diào)用處理器傳給我們有什么用。
第一個參數(shù)proxy的作用:
(1)可以通過反射獲取代理對象的信息,同時可以反復(fù)調(diào)用代理對象。
注意:this指向的是當(dāng)前類,而不是代理類即$Proxy0。
proxy解釋說明參考文檔連接:http://stackoverflow.com/questions/22930195/understanding-proxy-arguments-of-the-invoke-method-of-java-lang-reflect-invoca
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之2-3-4樹
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之2-3-4樹,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01Java httpcomponents發(fā)送get post請求代碼實例
這篇文章主要介紹了Java httpcomponents發(fā)送get post請求代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09基于spring+springmvc+hibernate 整合深入剖析
這篇文章主要介紹了于spring+springmvc+hibernate整合實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10java實時監(jiān)控文件行尾內(nèi)容的實現(xiàn)
這篇文章主要介紹了java實時監(jiān)控文件行尾內(nèi)容的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02