深入剖析java代理模式
Java中代理模式有著廣泛的應(yīng)用,AOP就是最典型的應(yīng)用。
Java中代理模式一般涉及到的角色有
1、抽象角色:一般是個接口,Java原生的代理模式也只支持接口代理
2、真實角色:實現(xiàn)抽象接口的真實類,又叫委托類。
3、代理角色:代理角色內(nèi)部包含了真實角色的引用,且實現(xiàn)了與真實角色相同的接口,相當(dāng)于對真實角色進(jìn)行了封裝。這樣,代理角色可以執(zhí)行真實角色的操作,還能額外附加自己的操作。
靜態(tài)代理
我們通過代碼,實現(xiàn)一個簡單的靜態(tài)代理。模擬一個場景,網(wǎng)易為暴雪代理魔獸世界。
抽象接口:魔獸世界——Wow
-
Java 代碼
-
查看代碼打印?
接口中一個待實現(xiàn)的方法:TBC(燃燒的遠(yuǎn)征)
實現(xiàn)類:暴雪——Blizzard
Java 代碼
查看代碼打印?
實現(xiàn)類很簡單,暴雪自己實現(xiàn)了tbc這個接口,開始燃燒的遠(yuǎn)征。
代理類:網(wǎng)易——NetEasy
Java 代碼
查看代碼打印?
在代理類網(wǎng)易中,包含了暴雪的實例對象,網(wǎng)易在實現(xiàn)tbc接口時,加入了自己的邏輯,并執(zhí)行了暴雪實現(xiàn)的方法。
寫個工廠方法獲取Wow的實例,對客戶隱藏了實現(xiàn),玩家不知道是返回的是暴雪還是網(wǎng)易。
-
Java 代碼
-
查看代碼打印?
寫個main函數(shù)測試下
Java 代碼
查看代碼打印?
這就是最簡單的一個靜態(tài)代理的模式,功能就是網(wǎng)易為暴雪代理魔獸世界。
靜態(tài)代理的缺陷:
1、如果接口中方法很多,代理類對每個方法都做代理,那么靜態(tài)代理類規(guī)模會非常龐大。
2、如果接口中增加/減少了一個方法,實現(xiàn)類和代理類都需要做相應(yīng)的修改,牽一發(fā)動全身。
動態(tài)代理
Java動態(tài)代理,程序并不難寫,但是想弄懂其中的原理,還是需要仔細(xì)研究jdk源碼的。
還是以上面的例子說明,魔獸世界Wow這個接口,還有暴雪Blizzard這個實現(xiàn)類,都無須改動。我們增加一個動態(tài)代理處理器和工廠方法。
Java 代碼
查看代碼打印?
DynamicHandler這個類很重要,它實現(xiàn)了InvocationHandler接口。Java中要實現(xiàn)動態(tài)代理,需要實現(xiàn)InvocationHandler這個接口。但是實現(xiàn)InvocationHandler接口的類,并不是動態(tài)代理類,動態(tài)代理類會在運行時生成,jvm在運行時,會根據(jù)實現(xiàn)了InvocationHandler接口的類生成一個動態(tài)代理類。因此有些文章將實現(xiàn)了InvocationHandler接口的類定義為動態(tài)代理類,這個是有欠妥當(dāng)?shù)模鋵嵵皇且粋€handler,真正的動態(tài)代理類是運行時生成的。
我們看DynamicHandler這個類,它實現(xiàn)了InvocationHandler接口中的invoke方法,invoke方法中植入了自己的邏輯,并用反射的方式對委托類進(jìn)行方法調(diào)用。僅僅看這個實現(xiàn),其實很模糊,這個invoke(Object proxy, Method method, Object[] args)方法在哪里調(diào)用的?
我么再看下動態(tài)工廠,它會返回動態(tài)代理的實例。同樣,對于客戶而言,并不知道實現(xiàn)Wow接口的是委托類還是代理類。
Java 代碼
查看代碼打印?
看上面的代碼,最關(guān)鍵的莫過于Proxy.newProxyInstance(blizzard.getClass().getClassLoader(), blizzard.getClass().getInterfaces(), invocatioonHandler)這句了。這一句在運行時會創(chuàng)建動態(tài)代理類并返回它的一個實例對象。我們看其源碼實現(xiàn)。Proxy.newProxyInstance(...)源碼較多,我們抽取其中的關(guān)鍵語句
Java 代碼
查看代碼打印?
中間省略了N句,
(1)Class> cl = getProxyClass0(loader, intfs)返回運行時生成的動態(tài)代理類。
(2)final Constructor> cons = cl.getConstructor(constructorParams)返回該代理類的構(gòu)造函數(shù)。
(3)cons.newInstance(new Object[]{h})則通過構(gòu)造函數(shù)創(chuàng)建代理類的實例對象,注意參數(shù)h就是傳遞的實現(xiàn)InvocationHandler接口的對象實例。
其中最關(guān)鍵的還是Class> cl = getProxyClass0(loader, intfs); ?這句生成了動態(tài)代理類。跟蹤源代碼,最終找到以下語句
Java 代碼
查看代碼打印?
ProxyGenerator.generateProxyClass()完成了生成字節(jié)碼的動作,這個方法可以在運行時產(chǎn)生一個描述代理類字節(jié)碼的byte[]數(shù)組。具體實現(xiàn)這里暫時不深究。
private static native Class> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
defineClass0則是一個native方法,根據(jù)classloader,字節(jié)碼數(shù)組等動態(tài)生成一個class類。
我們寫一個main方法,測試一下動態(tài)代理
Html 代碼
查看代碼打印?
由于代理類是動態(tài)生成的,一般來說,并不能找到其class文件。我們加上System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");這句話,這樣可以在磁盤中看到class文件,位于項目根目錄下的com/sun/proxy路徑下,文件名為$Proxy0.class。反編譯一下,看其源碼
Java 代碼
查看代碼打印?
省略了equals, hashcode, toString方法,和tbc()的實現(xiàn)是類似的。
可以看到$Proxy0這個類繼承了Proxy類并實現(xiàn)了Wow接口。
由于繼承了Proxy類,因此擁有了InvocationHandler的實現(xiàn)類的實例,上面在DynamicFactory中,我們是通過Proxy.newInstance(......)傳遞了InvocationHandler的實現(xiàn)類實例的。
$Proxy0中tbc()的實現(xiàn),this.h.invoke(this, m3, null); 其實就是調(diào)用了DynamicHandler中的invoke方法。
這樣一來,整個流程都串通了,我們終于弄清楚了整個流程。
總結(jié)
與靜態(tài)代理相比,動態(tài)代理將接口中的所有方法都通過InvocationHandler的invoke方法處理,比較靈活。
靜態(tài)代理的那些缺陷,動態(tài)代理基本都能解決。
從上面的實現(xiàn)中,我們還可以看到AOP的一個雛形。
Java中自帶的動態(tài)代理也有一定的局限性,它只是針對接口而言的,如果想對class對代理,則必須借助CGLib等開源jar包實現(xiàn)。而且關(guān)于其中的動態(tài)字節(jié)碼等關(guān)鍵技術(shù),還有待深入研究。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。