這篇文章主要介紹了JDK動態(tài)代理機(jī)制的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
成都創(chuàng)新互聯(lián)專注于尼木網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供尼木營銷型網(wǎng)站建設(shè),尼木網(wǎng)站制作、尼木網(wǎng)頁設(shè)計、尼木網(wǎng)站官網(wǎng)定制、重慶小程序開發(fā)服務(wù),打造尼木網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供尼木網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。前言
『動態(tài)代理』其實(shí)源于設(shè)計模式中的代理模式,而代理模式就是使用代理對象完成用戶請求,屏蔽用戶對真實(shí)對象的訪問。
舉個最簡單的例子,比如我們想要「FQ」訪問國外網(wǎng)站,因?yàn)槲覀儾]有墻掉所有國外的 IP,所以你可以將你的請求數(shù)據(jù)報發(fā)送到那些沒有被屏蔽的國外主機(jī)上,然后你通過配置國外主機(jī)將請求轉(zhuǎn)發(fā)到目的地并在得到響應(yīng)報文后轉(zhuǎn)發(fā)回我們國內(nèi)主機(jī)上。
這個例子中,國外主機(jī)就是一個代理對象,而那些被墻掉的主機(jī)就是真實(shí)對象,我們不能直接訪問到真實(shí)對象,但可以通過一個代理間接的訪問到。
代理模式的一個好處就是,所有的外部請求都經(jīng)過代理對象,而代理對象有權(quán)利控制是否允許你真正的訪問到真實(shí)對象,如果不合法的請求,代理對象完全可以拒絕你而不用實(shí)際麻煩到真實(shí)對象。
代理模式的一個最典型的應(yīng)用就是 Spring 框架,Spring 的 AOP 以面向切面式編程將實(shí)際的業(yè)務(wù)邏輯和相關(guān)日志異常等信息隔離開,而你每次對業(yè)務(wù)邏輯的請求都對應(yīng)的是一個代理對象,這個代理對象中除了進(jìn)行必要的權(quán)限檢查,日志打印,就是真實(shí)的業(yè)務(wù)邏輯處理塊。
靜態(tài)代理
代理模式的實(shí)現(xiàn)者主要有兩種,『靜態(tài)代理』和『動態(tài)代理』,這兩者的本質(zhì)區(qū)別就在于,前者的代理類是需要程序員手動編碼的,而后者的代理類是自動生成的。所以,這也是你幾乎沒有聽過『靜態(tài)代理』這個概念的原因,當(dāng)然,了解一下靜態(tài)代理自然更容易去理解『動態(tài)代理』。
有一點(diǎn)大家需要清楚,代理對象代理了真實(shí)對象所有的方法,也就是代理對象需要向外提供至少和真實(shí)對象一樣的方法名供調(diào)用,所以一個代理對象就需要定義出真實(shí)對象擁有的所有方法,包括父類中的方法。
我們看一個簡單的靜態(tài)代理示例:
為了說明問題,我們定義了一個 IService 接口,并讓我們的真實(shí)類繼承并實(shí)現(xiàn)該接口,這樣我們的真實(shí)類中就有兩個方法了。
那么代理類該怎樣定義才能完成對真實(shí)對象的代理呢?
一般來說,代理類的本質(zhì)就是,定義出真實(shí)類中所有的方法并在方法內(nèi)部添加一些其他操作,最后再調(diào)用真實(shí)類的該方法。
代理類要代理真實(shí)類中所有的方法,也就是說需要定義和真實(shí)類中那些方法簽名一模一樣的方法,而這些方法的內(nèi)部還是會間接調(diào)用真實(shí)類的該方法。
所以一般來說,代理類會選擇直接繼承真實(shí)類所有的接口和父類以便拿到真實(shí)類所有的父級方法簽名,也就是先代理所有的父級方法。
接著,代理真實(shí)類中非父級方法,以這里的例子來說,doService 方法就是真實(shí)類自己的方法,我們的代理類也要定義一個一模一樣方法簽名的方法對其進(jìn)行代理。
這樣,我們的代理類就算是完成了,以后對于真實(shí)類中所有方法的調(diào)用都可以通過代理類進(jìn)行代理。像這樣:
public static void main(String[] args){ realClass realClass = new realClass(); ProxyClass proxyClass = new ProxyClass(realClass); proxyClass.sayHello(); proxyClass.doService(); }
proxyClass 作為一個代理類對象,可以代理真實(shí)類中所有的方法,并在這些方法執(zhí)行之前,打印了一些「無關(guān)緊要」的信息。
代理模式的一個基本實(shí)現(xiàn)思路基本是這樣,但是動態(tài)代理不同于這種靜態(tài)代理的一點(diǎn)在于,動態(tài)代理不用我們一個一個方法的定義,虛擬機(jī)會自動為你生成這些方法。
JDK 動態(tài)代理機(jī)制
動態(tài)代理區(qū)別于靜態(tài)代理的一點(diǎn)是,動態(tài)代理的代理類由虛擬機(jī)在運(yùn)行時動態(tài)創(chuàng)建并于虛擬機(jī)卸載時清除。
我們復(fù)用上述靜態(tài)代理中使用的類,看看 JDK 的動態(tài)代理具體是如何做到代理出某個類實(shí)例的所有方法的。
定義一個 Handler 處理類:
Main 函數(shù)中調(diào)用 JDK 的動態(tài)代理 API 生成代理類實(shí)例:
涉及的代碼還是比較多的,我們一點(diǎn)點(diǎn)來分析。首先,realClass 作為我們的被代理類實(shí)現(xiàn)了接口 IService 并在內(nèi)部定義了一個自己的方法 doService。
接著,我們定義了一個處理類,它繼承了接口 InvocationHandler 并實(shí)現(xiàn)了其唯一申明的 invoke 方法。除此之外,我們還得聲明一個成員字段用于存儲真實(shí)對象,也就是被代理對象,因?yàn)槲覀兇淼娜魏畏椒ɑ旧隙际腔谡鎸?shí)對象的相關(guān)方法的。
關(guān)于這個 invoke 方法的作用以及各個形式參數(shù)的意義,待會我們反射代理類源碼的時候再做詳細(xì)的分析。
最后,定義好我們的處理類,基本上就可以進(jìn)行基于 JDK 的動態(tài)代理了。核心的方法是 Proxy 類的 newProxyInstance 方法,該方法有三個參數(shù),其一是一個類加載器,其二是被代理類實(shí)現(xiàn)的所有接口集合,其三是我們自定義的處理器類。
虛擬機(jī)會在運(yùn)行時使用你提供的類加載器,將所有指定的接口類加載進(jìn)方法區(qū),然后反射讀取這些接口中的方法并結(jié)合處理器類生成一個代理類型。
最后一句話可能有點(diǎn)抽象,如何「結(jié)合處理器類生成一個代理類型」?這一點(diǎn)我們通過指定虛擬機(jī)啟動參數(shù),讓它保存下來生成的代理類的 Class 文件。
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
我們通過第三方工具反編譯這個 Class 文件,內(nèi)容比較多,我們拆分了分析:
首先,這個代理類的名字是很隨意的,一個程序中如果有多個代理類要生成,「$Proxy + 數(shù)字」就是它們的類名。
接著,你會注意到這個代理類繼承 Proxy 類和我們指定的接口 IService(之前如果指定多個接口,這里就會繼承多個接口)。
然后你會發(fā)現(xiàn),這個構(gòu)造器需要一個 InvocationHandler 類型的參數(shù),并且構(gòu)造器的主體就是將這個 InvocationHandler 實(shí)例傳遞到父類 Proxy 的對應(yīng)字段進(jìn)行保存,這也是為什么所有的代理類都必須使用 Proxy 作為父類的一個原因,就是為了公用父類中的 InvocationHandler 字段。后面我們會知道,這一個小小的設(shè)計將導(dǎo)致基于 JDK 的動態(tài)代理存在一個致命性的缺點(diǎn),待會介紹。
這一塊內(nèi)容也算是代理類中較為重要的部分了,它將于虛擬機(jī)靜態(tài)初始化這個代理類的時候執(zhí)行。這一大段代碼就是完成反射出所有接口中方法的功能,所有被反射出來的方法都對應(yīng)一個 Method 類型的字段進(jìn)行存儲。
除此之外,虛擬機(jī)還反射了 Object 中的三個常用方法,也就是說,代理類還會代理真實(shí)對象從 Object 那繼承來的這三個方法。
最后一部分我們看到的就是,虛擬機(jī)根據(jù)靜態(tài)初始化代碼塊反射出來所有待代理的方法,為它們生成代理的方法。
這些方法看起來好多代碼,其實(shí)就一行代碼,從父類 Proxy 中取出構(gòu)造實(shí)例化時存入的處理器類,并調(diào)用它的 invoke 方法。
方法的參數(shù)基本一樣,第一個參數(shù)是當(dāng)前代理類實(shí)例(事實(shí)證明這個參數(shù)傳過去并沒什么用),第二個參數(shù)是 Method 方法實(shí)例,第三個參數(shù)是方法的形式參數(shù)集合,如果沒有就是 null。
而這會兒我們再來看看當(dāng)初自定的處理器類:
所有的代理類方法內(nèi)部都會調(diào)用處理器類的 invoke 方法并傳入被代理類的當(dāng)前方法,而這個 invoke 方法可以選擇去讓 method 正常被調(diào)用,也可以跳過 method 的調(diào)用,甚至可以在 method 真正被調(diào)用前后做一些額外的事情。
這,就是 JDK 動態(tài)代理的核心思想,我們稍微總結(jié)一下整個調(diào)用流程。
首先,一個處理器類的定義是必不可少的,它的內(nèi)部必須得關(guān)聯(lián)一個真實(shí)對象,即被代理類實(shí)例。
接著,我們從外部調(diào)用代理類的任一方法,從反編譯的源碼我們知道,代理類方法會轉(zhuǎn)而去調(diào)用處理器的 invoke 方法并傳入方法簽名和方法的形式參數(shù)集合。
最后,方法能否得到正常的調(diào)用取決于處理器 invoke 方法體是否實(shí)實(shí)在在去調(diào)用了 method 方法。
其實(shí),基于 JDK 實(shí)現(xiàn)的的動態(tài)代理是有缺陷的,并且這些缺陷是不易修復(fù)的,所以才有了 CGLIB 的流行。
一些缺陷與不足
單一的代理機(jī)制
不知道大家注意到我們上述的例子沒有,虛擬機(jī)生成的代理類為了公用 Proxy 類中的 InvocationHandler 字段來存儲自己的處理器類實(shí)例而繼承了 Proxy 類,那說明了什么?
Java 的單根繼承告訴你,代理類不能再繼承任何別的類了,那么被代理類父類中的方法自然就無從獲取,即代理類無法代理真實(shí)類中父類的任何方法。
除此之外的是另一個小細(xì)節(jié),不知道大家有沒有注意到,我特意這樣寫的。
這里的 sayHello 方法是實(shí)現(xiàn)的接口 IService,而 doService 方法則是獨(dú)屬于 realClass 自己的方法。但是我們從代理類中并沒有看到這個方法,也就是說這個方法沒有被代理。
所以說,JDK 的動態(tài)代理機(jī)制是單一的,它只能代理被代理類的接口集合中的方法。
不友好的返回值
大家注意一下,newProxyInstance 返回的是代理類 「$Proxy0」 的一個實(shí)例,但是它是以 Object 類型進(jìn)行返回的,而你又不能強(qiáng)轉(zhuǎn)這個 Object 實(shí)例到 「$Proxy0」 類型。
雖然我們知道這個 Object 實(shí)例其實(shí)就是 「$Proxy0」 類型,但編譯期是不存在這個 「$Proxy0」 類型的,編譯器自然不會允許你強(qiáng)轉(zhuǎn)為一個不存在的類型了。所以一般只會強(qiáng)轉(zhuǎn)為該代理類實(shí)現(xiàn)的接口之一。
realClass rc = new realClass(); MyHanlder hanlder = new MyHanlder(rc); IService obj = (IService)Proxy.newProxyInstance( rc.getClass().getClassLoader(), new Class[]{IService.class}, hanlder); obj.sayHello();
程序運(yùn)行輸出:
proxy begainning..... hello world..... proxy ending.....
那么問題又來了,假如我們的被代理類實(shí)現(xiàn)了多個接口,請問你該強(qiáng)轉(zhuǎn)為那個接口類型,現(xiàn)在假設(shè)被代理類實(shí)現(xiàn)了接口 A 和 B,那么最后的實(shí)例如果強(qiáng)轉(zhuǎn)為 A ,自然被代理類所實(shí)現(xiàn)的接口 B 中所有的方法你都不能調(diào)用,反之亦然。
這樣就直接導(dǎo)致一個結(jié)果,你得清楚哪個方法是哪個接口中的,調(diào)用某個方法之前強(qiáng)轉(zhuǎn)為對應(yīng)的接口,相當(dāng)不友好的設(shè)計。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“JDK動態(tài)代理機(jī)制的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!
分享標(biāo)題:JDK動態(tài)代理機(jī)制的示例分析-創(chuàng)新互聯(lián)
鏈接URL:http://jinyejixie.com/article44/dchshe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供ChatGPT、營銷型網(wǎng)站建設(shè)、微信公眾號、虛擬主機(jī)、外貿(mào)網(wǎng)站建設(shè)、品牌網(wǎng)站設(shè)計
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容