JDK動態(tài)代理機(jī)制的示例分析-創(chuàng)新互聯(lián)

這篇文章主要介紹了JDK動態(tài)代理機(jī)制的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

成都創(chuàng)新互聯(lián)專注于尼木網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供尼木營銷型網(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è)計模式中的代理模式,而代理模式就是使用代理對象完成用戶請求,屏蔽用戶對真實對象的訪問。

舉個最簡單的例子,比如我們想要「FQ」訪問國外網(wǎng)站,因為我們并沒有墻掉所有國外的 IP,所以你可以將你的請求數(shù)據(jù)報發(fā)送到那些沒有被屏蔽的國外主機(jī)上,然后你通過配置國外主機(jī)將請求轉(zhuǎn)發(fā)到目的地并在得到響應(yīng)報文后轉(zhuǎn)發(fā)回我們國內(nèi)主機(jī)上。

這個例子中,國外主機(jī)就是一個代理對象,而那些被墻掉的主機(jī)就是真實對象,我們不能直接訪問到真實對象,但可以通過一個代理間接的訪問到。

代理模式的一個好處就是,所有的外部請求都經(jīng)過代理對象,而代理對象有權(quán)利控制是否允許你真正的訪問到真實對象,如果不合法的請求,代理對象完全可以拒絕你而不用實際麻煩到真實對象。

代理模式的一個最典型的應(yīng)用就是 Spring 框架,Spring 的 AOP 以面向切面式編程將實際的業(yè)務(wù)邏輯和相關(guān)日志異常等信息隔離開,而你每次對業(yè)務(wù)邏輯的請求都對應(yīng)的是一個代理對象,這個代理對象中除了進(jìn)行必要的權(quán)限檢查,日志打印,就是真實的業(yè)務(wù)邏輯處理塊。

靜態(tài)代理

代理模式的實現(xiàn)者主要有兩種,『靜態(tài)代理』和『動態(tài)代理』,這兩者的本質(zhì)區(qū)別就在于,前者的代理類是需要程序員手動編碼的,而后者的代理類是自動生成的。所以,這也是你幾乎沒有聽過『靜態(tài)代理』這個概念的原因,當(dāng)然,了解一下靜態(tài)代理自然更容易去理解『動態(tài)代理』。

有一點大家需要清楚,代理對象代理了真實對象所有的方法,也就是代理對象需要向外提供至少和真實對象一樣的方法名供調(diào)用,所以一個代理對象就需要定義出真實對象擁有的所有方法,包括父類中的方法。

我們看一個簡單的靜態(tài)代理示例:

JDK動態(tài)代理機(jī)制的示例分析

JDK動態(tài)代理機(jī)制的示例分析

為了說明問題,我們定義了一個 IService 接口,并讓我們的真實類繼承并實現(xiàn)該接口,這樣我們的真實類中就有兩個方法了。

那么代理類該怎樣定義才能完成對真實對象的代理呢?

JDK動態(tài)代理機(jī)制的示例分析

一般來說,代理類的本質(zhì)就是,定義出真實類中所有的方法并在方法內(nèi)部添加一些其他操作,最后再調(diào)用真實類的該方法。

代理類要代理真實類中所有的方法,也就是說需要定義和真實類中那些方法簽名一模一樣的方法,而這些方法的內(nèi)部還是會間接調(diào)用真實類的該方法。

所以一般來說,代理類會選擇直接繼承真實類所有的接口和父類以便拿到真實類所有的父級方法簽名,也就是先代理所有的父級方法。

接著,代理真實類中非父級方法,以這里的例子來說,doService 方法就是真實類自己的方法,我們的代理類也要定義一個一模一樣方法簽名的方法對其進(jìn)行代理。

這樣,我們的代理類就算是完成了,以后對于真實類中所有方法的調(diào)用都可以通過代理類進(jìn)行代理。像這樣:

public static void main(String[] args){
 realClass realClass = new realClass();
 ProxyClass proxyClass = new ProxyClass(realClass);
 proxyClass.sayHello();
 proxyClass.doService();
}

proxyClass 作為一個代理類對象,可以代理真實類中所有的方法,并在這些方法執(zhí)行之前,打印了一些「無關(guān)緊要」的信息。

代理模式的一個基本實現(xiàn)思路基本是這樣,但是動態(tài)代理不同于這種靜態(tài)代理的一點在于,動態(tài)代理不用我們一個一個方法的定義,虛擬機(jī)會自動為你生成這些方法。

JDK 動態(tài)代理機(jī)制

動態(tài)代理區(qū)別于靜態(tài)代理的一點是,動態(tài)代理的代理類由虛擬機(jī)在運行時動態(tài)創(chuàng)建并于虛擬機(jī)卸載時清除。

我們復(fù)用上述靜態(tài)代理中使用的類,看看 JDK 的動態(tài)代理具體是如何做到代理出某個類實例的所有方法的。

JDK動態(tài)代理機(jī)制的示例分析

JDK動態(tài)代理機(jī)制的示例分析

定義一個 Handler 處理類:

JDK動態(tài)代理機(jī)制的示例分析

Main 函數(shù)中調(diào)用 JDK 的動態(tài)代理 API 生成代理類實例:

JDK動態(tài)代理機(jī)制的示例分析

涉及的代碼還是比較多的,我們一點點來分析。首先,realClass 作為我們的被代理類實現(xiàn)了接口 IService 并在內(nèi)部定義了一個自己的方法 doService。

接著,我們定義了一個處理類,它繼承了接口 InvocationHandler 并實現(xiàn)了其唯一申明的 invoke 方法。除此之外,我們還得聲明一個成員字段用于存儲真實對象,也就是被代理對象,因為我們代理的任何方法基本上都是基于真實對象的相關(guān)方法的。

關(guān)于這個 invoke 方法的作用以及各個形式參數(shù)的意義,待會我們反射代理類源碼的時候再做詳細(xì)的分析。

最后,定義好我們的處理類,基本上就可以進(jìn)行基于 JDK 的動態(tài)代理了。核心的方法是 Proxy 類的 newProxyInstance 方法,該方法有三個參數(shù),其一是一個類加載器,其二是被代理類實現(xiàn)的所有接口集合,其三是我們自定義的處理器類。

虛擬機(jī)會在運行時使用你提供的類加載器,將所有指定的接口類加載進(jìn)方法區(qū),然后反射讀取這些接口中的方法并結(jié)合處理器類生成一個代理類型。

最后一句話可能有點抽象,如何「結(jié)合處理器類生成一個代理類型」?這一點我們通過指定虛擬機(jī)啟動參數(shù),讓它保存下來生成的代理類的 Class 文件。

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

我們通過第三方工具反編譯這個 Class 文件,內(nèi)容比較多,我們拆分了分析:

JDK動態(tài)代理機(jī)制的示例分析

首先,這個代理類的名字是很隨意的,一個程序中如果有多個代理類要生成,「$Proxy + 數(shù)字」就是它們的類名。

接著,你會注意到這個代理類繼承 Proxy 類和我們指定的接口 IService(之前如果指定多個接口,這里就會繼承多個接口)。

然后你會發(fā)現(xiàn),這個構(gòu)造器需要一個 InvocationHandler 類型的參數(shù),并且構(gòu)造器的主體就是將這個 InvocationHandler 實例傳遞到父類 Proxy 的對應(yīng)字段進(jìn)行保存,這也是為什么所有的代理類都必須使用 Proxy 作為父類的一個原因,就是為了公用父類中的 InvocationHandler 字段。后面我們會知道,這一個小小的設(shè)計將導(dǎo)致基于 JDK 的動態(tài)代理存在一個致命性的缺點,待會介紹。

JDK動態(tài)代理機(jī)制的示例分析

這一塊內(nèi)容也算是代理類中較為重要的部分了,它將于虛擬機(jī)靜態(tài)初始化這個代理類的時候執(zhí)行。這一大段代碼就是完成反射出所有接口中方法的功能,所有被反射出來的方法都對應(yīng)一個 Method 類型的字段進(jìn)行存儲。

除此之外,虛擬機(jī)還反射了 Object 中的三個常用方法,也就是說,代理類還會代理真實對象從 Object 那繼承來的這三個方法。

JDK動態(tài)代理機(jī)制的示例分析

最后一部分我們看到的就是,虛擬機(jī)根據(jù)靜態(tài)初始化代碼塊反射出來所有待代理的方法,為它們生成代理的方法。

這些方法看起來好多代碼,其實就一行代碼,從父類 Proxy 中取出構(gòu)造實例化時存入的處理器類,并調(diào)用它的 invoke 方法。

方法的參數(shù)基本一樣,第一個參數(shù)是當(dāng)前代理類實例(事實證明這個參數(shù)傳過去并沒什么用),第二個參數(shù)是 Method 方法實例,第三個參數(shù)是方法的形式參數(shù)集合,如果沒有就是 null。

而這會兒我們再來看看當(dāng)初自定的處理器類:

JDK動態(tài)代理機(jī)制的示例分析

所有的代理類方法內(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)一個真實對象,即被代理類實例。

接著,我們從外部調(diào)用代理類的任一方法,從反編譯的源碼我們知道,代理類方法會轉(zhuǎn)而去調(diào)用處理器的 invoke 方法并傳入方法簽名和方法的形式參數(shù)集合。

最后,方法能否得到正常的調(diào)用取決于處理器 invoke 方法體是否實實在在去調(diào)用了 method 方法。

其實,基于 JDK 實現(xiàn)的的動態(tài)代理是有缺陷的,并且這些缺陷是不易修復(fù)的,所以才有了 CGLIB 的流行。

一些缺陷與不足

單一的代理機(jī)制

不知道大家注意到我們上述的例子沒有,虛擬機(jī)生成的代理類為了公用 Proxy 類中的 InvocationHandler 字段來存儲自己的處理器類實例而繼承了 Proxy 類,那說明了什么?

Java 的單根繼承告訴你,代理類不能再繼承任何別的類了,那么被代理類父類中的方法自然就無從獲取,即代理類無法代理真實類中父類的任何方法。

除此之外的是另一個小細(xì)節(jié),不知道大家有沒有注意到,我特意這樣寫的。

JDK動態(tài)代理機(jī)制的示例分析

這里的 sayHello 方法是實現(xiàn)的接口 IService,而 doService 方法則是獨屬于 realClass 自己的方法。但是我們從代理類中并沒有看到這個方法,也就是說這個方法沒有被代理。

所以說,JDK 的動態(tài)代理機(jī)制是單一的,它只能代理被代理類的接口集合中的方法。

不友好的返回值

JDK動態(tài)代理機(jī)制的示例分析

大家注意一下,newProxyInstance 返回的是代理類 「$Proxy0」 的一個實例,但是它是以 Object 類型進(jìn)行返回的,而你又不能強(qiáng)轉(zhuǎn)這個 Object 實例到 「$Proxy0」 類型。

雖然我們知道這個 Object 實例其實就是 「$Proxy0」 類型,但編譯期是不存在這個 「$Proxy0」 類型的,編譯器自然不會允許你強(qiáng)轉(zhuǎn)為一個不存在的類型了。所以一般只會強(qiáng)轉(zhuǎn)為該代理類實現(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();

程序運行輸出:

proxy begainning.....
hello world.....
proxy ending.....

那么問題又來了,假如我們的被代理類實現(xiàn)了多個接口,請問你該強(qiáng)轉(zhuǎn)為那個接口類型,現(xiàn)在假設(shè)被代理類實現(xiàn)了接口 A 和 B,那么最后的實例如果強(qiáng)轉(zhuǎn)為 A ,自然被代理類所實現(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://muchs.cn/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)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)