JDK動態(tài)代理接口和接口實(shí)現(xiàn)類深入詳解
前言
在Java開發(fā)中,JDK動態(tài)代理是一種非常有用的技術(shù),它允許開發(fā)者在不修改目標(biāo)類代碼的情況下,為目標(biāo)類添加額外的功能。然而,JDK動態(tài)代理的使用有一些限制,特別是它只能代理接口和接口實(shí)現(xiàn)類。本文將深入探討這一限制的原因。
1.JDK動態(tài)代理原理
下面是一個簡單的動態(tài)代理的例子
/** * 要代理的接口 */ public interface Target{ String say(); } /** * 真實(shí)調(diào)用對象 */ public class RealTarget { public String invoke(){ return "i'm proxy"; } } /** * JDK代理類生成 */ public class JDKProxy implements InvocationHandler { private Object target; JDKProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] paramValues) { System.out.println("Before method invocation"); Object result = ((RealTarget)target).invoke(); System.out.println("After method invocation"); return result; } } /** * 測試?yán)? */ public class TestProxy { public static void main(String[] args){ // 構(gòu)建代理器 JDKProxy proxy = new JDKProxy(new RealTarget()); ClassLoader classLoader = ClassLoaderUtils.getCurrentClassLoader(); // 把生成的代理類保存到文件 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); // 生成代理類 Target test = (Target) Proxy.newProxyInstance(classLoader, new Class[]{Target.class}, proxy); // 方法調(diào)用 System.out.println(test.say()); } }
這段代碼想表達(dá)的意思就是:給 Target接口生成一個動態(tài)代理類,并調(diào)用接口 say() 方法,但真實(shí)返回的值居然是來自 RealTarget里面的 invoke() 方法返回值。你看,短短50行的代碼,就完成了這個功能,是不是還挺有意思的?
那既然重點(diǎn)是代理類的生成,那我們就去看下 Proxy.newProxyInstance 里面究竟發(fā)生了什么?
一起看下下面的流程圖,具體代碼細(xì)節(jié)你可以對照著 JDK 的源碼看(上文中有類和方法,可以直接定位),我是按照 1.7.X 版本梳理的。
在生成字節(jié)碼的那個地方,也就是 ProxyGenerator.generateProxyClass() 方法里面,通過代碼我們可以看到,里面是用參數(shù) saveGeneratedFiles 來控制是否把生成的字節(jié)碼保存到本地磁盤。同時(shí)為了更直觀地了解代理的本質(zhì),我們需要把參數(shù) saveGeneratedFiles 設(shè)置成true,但這個參數(shù)的值是由key為“sun.misc.ProxyGenerator.saveGeneratedFiles”的Property來控制的,動態(tài)生成的類會保存在工程根目錄下的 com/sun/proxy 目錄里面。現(xiàn)在我們找到剛才生成的 $Proxy0.class,通過反編譯工具打開class文件,你會看到這樣的代碼:
package com.sun.proxy; import com.proxy.Hello; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Target { private static Method m3; private static Method m1; private static Method m0; private static Method m2; public $Proxy0(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } public final String say() { try { return (String)this.h.invoke(this, m3, null); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final boolean equals(Object paramObject) { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } static { try { m3 = Class.forName("com.proxy.Target").getMethod("say", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException noSuchMethodException) { throw new NoSuchMethodError(noSuchMethodException.getMessage()); } catch (ClassNotFoundException classNotFoundException) { throw new NoClassDefFoundError(classNotFoundException.getMessage()); } } }
我們可以看到 $Proxy0 類里面有一個跟 Target一樣簽名的 say() 方法,其中 this.h 綁定的是剛才傳入的 JDKProxy 對象,所以當(dāng)我們調(diào)用 Target.say() 的時(shí)候,其實(shí)它是被轉(zhuǎn)發(fā)到了JDKProxy.invoke()。到這兒,整個魔術(shù)過程就透明了。
2.回答JDK動態(tài)代理的疑問
為何只能代理具有接口的類
這是因?yàn)镴DK動態(tài)代理的機(jī)制所限。在Java中,動態(tài)代理通過Proxy.newProxyInstance()
方法實(shí)現(xiàn),此方法要求傳入一個接口類作為被代理對象。這源于JDK動態(tài)代理的底層實(shí)現(xiàn):它在程序運(yùn)行時(shí)動態(tài)生成一個名為$Proxy0
的代理類,該代理類繼承自java.lang.reflect.Proxy
并實(shí)現(xiàn)了被代理的接口。由于Java不支持多重繼承,每個動態(tài)代理類都繼承自Proxy
,因此只能代理接口,而無法代理具體實(shí)現(xiàn)類。
JDK動態(tài)代理能否代理類
JDK中的Proxy
類主要用于保存動態(tài)代理的處理器InvocationHandler
。理論上,如果不通過Proxy
類而直接在動態(tài)生成的代理類內(nèi)部設(shè)置處理器,可能實(shí)現(xiàn)對類的動態(tài)代理。然而,JDK的設(shè)計(jì)者并未采取這種方式,這主要是出于設(shè)計(jì)上的考慮和限制。
為何這樣設(shè)計(jì)
- 使用場景與需求:動態(tài)代理的主要用途是在不修改原始實(shí)現(xiàn)的前提下,對方法進(jìn)行攔截以實(shí)現(xiàn)功能增強(qiáng)或擴(kuò)展。在實(shí)際開發(fā)中,基于接口編程是常見模式,因此基于接口實(shí)現(xiàn)動態(tài)代理符合需求和場景。當(dāng)然,也存在沒有實(shí)現(xiàn)接口的類,此時(shí)JDK動態(tài)代理無法滿足需求。
- 代碼重用與擴(kuò)展性:在Java中,類的設(shè)計(jì)更注重共性能力的抽象,以提高代碼的重用性和擴(kuò)展性。動態(tài)代理也遵循這一原則,它封裝了代理類的生成邏輯、接口判斷以及
InvocationHandler
的持有等,將這些抽象邏輯放在Proxy
父類中是一個合理的選擇。
其他實(shí)現(xiàn)方式
對于需要代理沒有接口的類,可以選擇使用CGLIB動態(tài)代理。CGLIB通過動態(tài)生成被代理類的子類,并重寫非final
修飾的方法,在子類中攔截父類方法的調(diào)用,從而實(shí)現(xiàn)動態(tài)代理。這種方式彌補(bǔ)了JDK動態(tài)代理只能代理接口的不足。
3.總結(jié)
JDK動態(tài)代理是Java中一種強(qiáng)大而靈活的技術(shù),它允許在不修改原始代碼的情況下對目標(biāo)對象的方法進(jìn)行功能增強(qiáng)。然而,由于其基于接口的代理機(jī)制,它只能代理接口和接口實(shí)現(xiàn)類。對于需要代理沒有實(shí)現(xiàn)接口的類的情況,可以考慮使用CGLIB動態(tài)代理等替代方案。在實(shí)際開發(fā)中,應(yīng)根據(jù)具體需求選擇合適的代理機(jī)制,以實(shí)現(xiàn)最佳的性能和可維護(hù)性。
到此這篇關(guān)于JDK動態(tài)代理接口和接口實(shí)現(xiàn)類深入詳解的文章就介紹到這了,更多相關(guān)JDK動態(tài)代理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Feign遠(yuǎn)程調(diào)用Multipartfile參數(shù)處理
這篇文章主要介紹了Feign遠(yuǎn)程調(diào)用Multipartfile參數(shù)處理,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Spring Boot 整合 TKMybatis 二次簡化持久層代碼的實(shí)現(xiàn)
這篇文章主要介紹了Spring Boot 整合 TKMybatis 二次簡化持久層代碼的實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Win10系統(tǒng)下配置java環(huán)境變量的全過程
這篇文章主要給大家介紹了關(guān)于Win10系統(tǒng)下配置java環(huán)境變量的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Java遞歸調(diào)用如何實(shí)現(xiàn)數(shù)字的逆序輸出方式
這篇文章主要介紹了Java遞歸調(diào)用如何實(shí)現(xiàn)數(shù)字的逆序輸出方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04idea?compile項(xiàng)目正常啟動項(xiàng)目的時(shí)候build失敗報(bào)“找不到符號”等問題及解決方案
這篇文章主要介紹了idea?compile項(xiàng)目正常,啟動項(xiàng)目的時(shí)候build失敗,報(bào)“找不到符號”等問題,這種問題屬于lombok編譯失敗導(dǎo)致,可能原因是依賴jar包沒有更新到最新版本,需要的朋友可以參考下2023-10-10Java如何使用ReentrantLock實(shí)現(xiàn)長輪詢
這篇文章主要介紹了如何使用ReentrantLock實(shí)現(xiàn)長輪詢,對ReentrantLock感興趣的同學(xué),可以參考下2021-04-04SpringBoot集成ElasticSearch的示例代碼
Elasticsearch是用Java語言開發(fā)的,并作為Apache許可條款下的開放源碼發(fā)布,是一種流行的企業(yè)級搜索引擎,本文給大家介紹SpringBoot集成ElasticSearch的示例代碼,感興趣的朋友一起看看吧2022-02-02