JDK動(dòng)態(tài)代理接口和接口實(shí)現(xiàn)類深入詳解
前言
在Java開發(fā)中,JDK動(dòng)態(tài)代理是一種非常有用的技術(shù),它允許開發(fā)者在不修改目標(biāo)類代碼的情況下,為目標(biāo)類添加額外的功能。然而,JDK動(dòng)態(tài)代理的使用有一些限制,特別是它只能代理接口和接口實(shí)現(xiàn)類。本文將深入探討這一限制的原因。
1.JDK動(dòng)態(tài)代理原理
下面是一個(gè)簡(jiǎn)單的動(dòng)態(tài)代理的例子
/**
* 要代理的接口
*/
public interface Target{
String say();
}
/**
* 真實(shí)調(diào)用對(duì)象
*/
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;
}
}
/**
* 測(cè)試?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接口生成一個(gè)動(dòng)態(tài)代理類,并調(diào)用接口 say() 方法,但真實(shí)返回的值居然是來自 RealTarget里面的 invoke() 方法返回值。你看,短短50行的代碼,就完成了這個(gè)功能,是不是還挺有意思的?
那既然重點(diǎn)是代理類的生成,那我們就去看下 Proxy.newProxyInstance 里面究竟發(fā)生了什么?
一起看下下面的流程圖,具體代碼細(xì)節(jié)你可以對(duì)照著 JDK 的源碼看(上文中有類和方法,可以直接定位),我是按照 1.7.X 版本梳理的。

在生成字節(jié)碼的那個(gè)地方,也就是 ProxyGenerator.generateProxyClass() 方法里面,通過代碼我們可以看到,里面是用參數(shù) saveGeneratedFiles 來控制是否把生成的字節(jié)碼保存到本地磁盤。同時(shí)為了更直觀地了解代理的本質(zhì),我們需要把參數(shù) saveGeneratedFiles 設(shè)置成true,但這個(gè)參數(shù)的值是由key為“sun.misc.ProxyGenerator.saveGeneratedFiles”的Property來控制的,動(dòng)態(tài)生成的類會(huì)保存在工程根目錄下的 com/sun/proxy 目錄里面?,F(xiàn)在我們找到剛才生成的 $Proxy0.class,通過反編譯工具打開class文件,你會(huì)看到這樣的代碼:
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 類里面有一個(gè)跟 Target一樣簽名的 say() 方法,其中 this.h 綁定的是剛才傳入的 JDKProxy 對(duì)象,所以當(dāng)我們調(diào)用 Target.say() 的時(shí)候,其實(shí)它是被轉(zhuǎn)發(fā)到了JDKProxy.invoke()。到這兒,整個(gè)魔術(shù)過程就透明了。
2.回答JDK動(dòng)態(tài)代理的疑問
為何只能代理具有接口的類
這是因?yàn)镴DK動(dòng)態(tài)代理的機(jī)制所限。在Java中,動(dòng)態(tài)代理通過Proxy.newProxyInstance()方法實(shí)現(xiàn),此方法要求傳入一個(gè)接口類作為被代理對(duì)象。這源于JDK動(dòng)態(tài)代理的底層實(shí)現(xiàn):它在程序運(yùn)行時(shí)動(dòng)態(tài)生成一個(gè)名為$Proxy0的代理類,該代理類繼承自java.lang.reflect.Proxy并實(shí)現(xiàn)了被代理的接口。由于Java不支持多重繼承,每個(gè)動(dòng)態(tài)代理類都繼承自Proxy,因此只能代理接口,而無法代理具體實(shí)現(xiàn)類。
JDK動(dòng)態(tài)代理能否代理類
JDK中的Proxy類主要用于保存動(dòng)態(tài)代理的處理器InvocationHandler。理論上,如果不通過Proxy類而直接在動(dòng)態(tài)生成的代理類內(nèi)部設(shè)置處理器,可能實(shí)現(xiàn)對(duì)類的動(dòng)態(tài)代理。然而,JDK的設(shè)計(jì)者并未采取這種方式,這主要是出于設(shè)計(jì)上的考慮和限制。
為何這樣設(shè)計(jì)
- 使用場(chǎng)景與需求:動(dòng)態(tài)代理的主要用途是在不修改原始實(shí)現(xiàn)的前提下,對(duì)方法進(jìn)行攔截以實(shí)現(xiàn)功能增強(qiáng)或擴(kuò)展。在實(shí)際開發(fā)中,基于接口編程是常見模式,因此基于接口實(shí)現(xiàn)動(dòng)態(tài)代理符合需求和場(chǎng)景。當(dāng)然,也存在沒有實(shí)現(xiàn)接口的類,此時(shí)JDK動(dòng)態(tài)代理無法滿足需求。
- 代碼重用與擴(kuò)展性:在Java中,類的設(shè)計(jì)更注重共性能力的抽象,以提高代碼的重用性和擴(kuò)展性。動(dòng)態(tài)代理也遵循這一原則,它封裝了代理類的生成邏輯、接口判斷以及
InvocationHandler的持有等,將這些抽象邏輯放在Proxy父類中是一個(gè)合理的選擇。
其他實(shí)現(xiàn)方式
對(duì)于需要代理沒有接口的類,可以選擇使用CGLIB動(dòng)態(tài)代理。CGLIB通過動(dòng)態(tài)生成被代理類的子類,并重寫非final修飾的方法,在子類中攔截父類方法的調(diào)用,從而實(shí)現(xiàn)動(dòng)態(tài)代理。這種方式彌補(bǔ)了JDK動(dòng)態(tài)代理只能代理接口的不足。
3.總結(jié)
JDK動(dòng)態(tài)代理是Java中一種強(qiáng)大而靈活的技術(shù),它允許在不修改原始代碼的情況下對(duì)目標(biāo)對(duì)象的方法進(jìn)行功能增強(qiáng)。然而,由于其基于接口的代理機(jī)制,它只能代理接口和接口實(shí)現(xiàn)類。對(duì)于需要代理沒有實(shí)現(xiàn)接口的類的情況,可以考慮使用CGLIB動(dòng)態(tài)代理等替代方案。在實(shí)際開發(fā)中,應(yīng)根據(jù)具體需求選擇合適的代理機(jī)制,以實(shí)現(xiàn)最佳的性能和可維護(hù)性。
到此這篇關(guān)于JDK動(dòng)態(tài)代理接口和接口實(shí)現(xiàn)類深入詳解的文章就介紹到這了,更多相關(guān)JDK動(dòng)態(tài)代理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Feign遠(yuǎn)程調(diào)用Multipartfile參數(shù)處理
這篇文章主要介紹了Feign遠(yuǎn)程調(diào)用Multipartfile參數(shù)處理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Spring Boot 整合 TKMybatis 二次簡(jiǎn)化持久層代碼的實(shí)現(xiàn)
這篇文章主要介紹了Spring Boot 整合 TKMybatis 二次簡(jiǎn)化持久層代碼的實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Win10系統(tǒng)下配置java環(huán)境變量的全過程
這篇文章主要給大家介紹了關(guān)于Win10系統(tǒng)下配置java環(huán)境變量的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Java遞歸調(diào)用如何實(shí)現(xiàn)數(shù)字的逆序輸出方式
這篇文章主要介紹了Java遞歸調(diào)用如何實(shí)現(xiàn)數(shù)字的逆序輸出方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
idea?compile項(xiàng)目正常啟動(dòng)項(xiàng)目的時(shí)候build失敗報(bào)“找不到符號(hào)”等問題及解決方案
這篇文章主要介紹了idea?compile項(xiàng)目正常,啟動(dòng)項(xiàng)目的時(shí)候build失敗,報(bào)“找不到符號(hào)”等問題,這種問題屬于lombok編譯失敗導(dǎo)致,可能原因是依賴jar包沒有更新到最新版本,需要的朋友可以參考下2023-10-10
Java如何使用ReentrantLock實(shí)現(xiàn)長(zhǎng)輪詢
這篇文章主要介紹了如何使用ReentrantLock實(shí)現(xiàn)長(zhǎng)輪詢,對(duì)ReentrantLock感興趣的同學(xué),可以參考下2021-04-04
SpringBoot集成ElasticSearch的示例代碼
Elasticsearch是用Java語言開發(fā)的,并作為Apache許可條款下的開放源碼發(fā)布,是一種流行的企業(yè)級(jí)搜索引擎,本文給大家介紹SpringBoot集成ElasticSearch的示例代碼,感興趣的朋友一起看看吧2022-02-02

