
微信搜索:碼農(nóng)StayUp
主頁(yè)地址:https://gozhuyinglong.github.io
JDK動(dòng)態(tài)代理是指:代理類實(shí)例在程序運(yùn)行時(shí),由JVM根據(jù)反射機(jī)制動(dòng)態(tài)的生成。也就是說(shuō)代理類不是用戶自己定義的,而是由JVM生成的。
由于其原理是通過(guò)Java反射機(jī)制實(shí)現(xiàn)的,所以在學(xué)習(xí)前,要對(duì)反射機(jī)制有一定的了解。傳送門:Java反射機(jī)制:跟著代碼學(xué)反射
下面是本篇講述內(nèi)容:

1. JDK動(dòng)態(tài)代理的核心類
JDK動(dòng)態(tài)代理有兩大核心類,它們都在Java的反射包下(java.lang.reflect),分別為InvocationHandler接口和Proxy類。
1.1 InvocationHandler接口
代理實(shí)例的調(diào)用處理器需要實(shí)現(xiàn)
InvocationHandler接口,并且每個(gè)代理實(shí)例都有一個(gè)關(guān)聯(lián)的調(diào)用處理器。當(dāng)一個(gè)方法在代理實(shí)例上被調(diào)用時(shí),這個(gè)方法調(diào)用將被編碼并分派到其調(diào)用處理器的invoke方法上。
也就是說(shuō),我們創(chuàng)建的每一個(gè)代理實(shí)例都要有一個(gè)關(guān)聯(lián)的InvocationHandler,并且在調(diào)用代理實(shí)例的方法時(shí),會(huì)被轉(zhuǎn)到InvocationHandler的invoke方法上。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
該invoke方法的作用是:處理代理實(shí)例上的方法調(diào)用并返回結(jié)果。
其有三個(gè)參數(shù),分別為:
- proxy:是調(diào)用該方法的代理實(shí)例。
-
method:是在代理實(shí)例上調(diào)用的接口方法對(duì)應(yīng)的
Method實(shí)例。 -
args:一個(gè)
Object數(shù)組,是在代理實(shí)例上的方法調(diào)用中傳遞的參數(shù)值。如果接口方法為無(wú)參,則該值為null。
其返回值為:調(diào)用代理實(shí)例上的方法的返回值。
1.2 Proxy類
Proxy類提供了創(chuàng)建動(dòng)態(tài)代理類及其實(shí)例的靜態(tài)方法,該類也是動(dòng)態(tài)代理類的超類。
代理類具有以下屬性:
- 代理類的名稱以 “$Proxy” 開(kāi)頭,后面跟著一個(gè)數(shù)字序號(hào)。
- 代理類繼承了
Proxy類。 - 代理類實(shí)現(xiàn)了創(chuàng)建時(shí)指定的接口(JDK動(dòng)態(tài)代理是面向接口的)。
- 每個(gè)代理類都有一個(gè)公共構(gòu)造函數(shù),它接受一個(gè)參數(shù),即接口
InvocationHandler的實(shí)現(xiàn),用于設(shè)置代理實(shí)例的調(diào)用處理器。
Proxy提供了兩個(gè)靜態(tài)方法,用于獲取代理對(duì)象。
1.2.1 getProxyClass
用于獲取代理類的Class對(duì)象,再通過(guò)調(diào)用構(gòu)造函數(shù)創(chuàng)建代理實(shí)例。
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
該方法有兩個(gè)參數(shù):
- loader:為類加載器。
-
intefaces:為接口的
Class對(duì)象數(shù)組。
返回值為動(dòng)態(tài)代理類的Class對(duì)象。
1.2.2 newProxyInstance
用于創(chuàng)建一個(gè)代理實(shí)例。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
該方法有三個(gè)參數(shù):
- loader:為類加載器。
-
interfaces:為接口的
Class對(duì)象數(shù)組。 - h:指定的調(diào)用處理器。
返回值為指定接口的代理類的實(shí)例。
1.3 小結(jié)
Proxy類主要用來(lái)獲取動(dòng)態(tài)代理對(duì)象,InvocationHandler接口主要用于方法調(diào)用的約束與增強(qiáng)。
2. 獲取代理實(shí)例的代碼示例
上一章中已經(jīng)介紹了獲取代理實(shí)例的兩個(gè)靜態(tài)方法,現(xiàn)在通過(guò)代碼示例來(lái)演示具體實(shí)現(xiàn)。
2.1 創(chuàng)建目標(biāo)接口及其實(shí)現(xiàn)類
JDK動(dòng)態(tài)代理是基于接口的,我們創(chuàng)建一個(gè)接口及其實(shí)現(xiàn)類。
Foo接口:
public interface Foo {
String ping(String name);
}
Foo接口的實(shí)現(xiàn)類RealFoo:
public class RealFoo implements Foo {
@Override
public String ping(String name) {
System.out.println("ping");
return "pong";
}
}
2.2 創(chuàng)建一個(gè)InvocationHandler
創(chuàng)建一個(gè)InvocationHandler接口的實(shí)現(xiàn)類MyInvocationHandler。該類的構(gòu)造方法參數(shù)為要代理的目標(biāo)對(duì)象。
invoke方法中的三個(gè)參數(shù)上面已經(jīng)介紹過(guò),通過(guò)調(diào)用method的invoke方法來(lái)完成方法的調(diào)用。
這里一時(shí)看不懂沒(méi)關(guān)系,后面源碼解析章節(jié)會(huì)進(jìn)行剖析。
public class MyInvocationHandler implements InvocationHandler {
// 目標(biāo)對(duì)象
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy - " + proxy.getClass());
System.out.println("method - " + method);
System.out.println("args - " + Arrays.toString(args));
return method.invoke(target, args);
}
}
2.3 方式一:通過(guò)getProxyClass方法獲取代理實(shí)例
具體實(shí)現(xiàn)步驟如下:
- 根據(jù)類加載器和接口數(shù)組獲取代理類的Class對(duì)象
- 過(guò)Class對(duì)象的構(gòu)造器創(chuàng)建一個(gè)實(shí)例(代理類的實(shí)例)
- 將代理實(shí)例強(qiáng)轉(zhuǎn)成目標(biāo)接口Foo(因?yàn)榇眍悓?shí)現(xiàn)了目標(biāo)接口,所以可以強(qiáng)轉(zhuǎn))。
- 最后使用代理進(jìn)行方法調(diào)用。
@Test
public void test1() throws Exception {
Foo foo = new RealFoo();
// 根據(jù)類加載器和接口數(shù)組獲取代理類的Class對(duì)象
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
// 通過(guò)Class對(duì)象的構(gòu)造器創(chuàng)建一個(gè)實(shí)例(代理類的實(shí)例)
Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class)
.newInstance(new MyInvocationHandler(foo));
// 調(diào)用 ping 方法,并輸出返回值
String value = fooProxy.ping("楊過(guò)");
System.out.println(value);
}
輸出結(jié)果:
proxy - class com.sun.proxy.$Proxy4
method - public abstract java.lang.String io.github.gozhuyinglong.proxy.Foo.ping(java.lang.String)
args - [楊過(guò)]
ping
pong
通過(guò)輸出結(jié)果可以看出:
- 代理類的名稱是以
$Proxy開(kāi)頭的。 - 方法實(shí)例為代理類調(diào)用的方法。
- 參數(shù)為代理類調(diào)用方法時(shí)傳的參數(shù)。
2.4 方式二:通過(guò)newProxyInstance方法獲取代理實(shí)例
通過(guò)這種方法是最簡(jiǎn)單的,也是推薦使用的,通過(guò)該方法可以直接獲取代理對(duì)象。
注:其實(shí)該方法后臺(tái)實(shí)現(xiàn)實(shí)際與上面使用getProxyClass方法的過(guò)程一樣。
@Test
public void test2() {
Foo foo = new RealFoo();
// 通過(guò)類加載器、接口數(shù)組和調(diào)用處理器,創(chuàng)建代理類的實(shí)例
Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[]{Foo.class},
new MyInvocationHandler(foo));
String value = fooProxy.ping("小龍女");
System.out.println(value);
}
2.5 通過(guò)Lambda表達(dá)式簡(jiǎn)化實(shí)現(xiàn)
其實(shí)InvocationHander接口也不用創(chuàng)建一個(gè)實(shí)現(xiàn)類,可以使用Lambad表達(dá)式進(jìn)行簡(jiǎn)化的實(shí)現(xiàn),如下代碼:
@Test
public void test3() {
Foo foo = new RealFoo();
Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[]{Foo.class},
(proxy, method, args) -> method.invoke(foo, args));
String value = fooProxy.ping("雕兄");
System.out.println(value);
}
3. 源碼解析
3.1 代理類$Proxy是什么樣子
JVM為我們自動(dòng)生成的代理類到底是什么樣子的呢?下面我們先來(lái)生成一下,再來(lái)看里面的構(gòu)造。
3.1.1 生成$Proxy的.class文件
JVM默認(rèn)不創(chuàng)建該.class文件,需要增加一個(gè)啟動(dòng)參數(shù):
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
在IDEA中點(diǎn)擊【Edit Configurations...】,打開(kāi) Run/Debug Configurations 配置框。

將上面啟動(dòng)參數(shù)加到【VM options】中,點(diǎn)擊【OK】即可。

再次運(yùn)行代碼,會(huì)在項(xiàng)目中的【com.sun.proxy】目錄中找到這個(gè).class文件,我這里是“$Proxy4.class”

3.1.2 為什么加上這段啟動(dòng)參數(shù)就能生成$Proxy的字節(jié)碼文件
在Proxy類中有個(gè)ProxyClassFactory靜態(tài)內(nèi)部類,該類主要作用就是生成靜態(tài)代理的。
其中有一段代碼ProxyGenerator.generateProxyClass用來(lái)生成代理類的.class文件。

其中變量saveGeneratedFiles便是引用了此啟動(dòng)參數(shù)的值。將該啟動(dòng)參數(shù)配置為true會(huì)生成.class文件。

3.1.3 這個(gè)代理類$Proxy到底是什么樣子呢
神秘的面紗即將揭露,前面很多未解之迷在這里可以找到答案!
打開(kāi)這個(gè)$Proxy文件,我這里生成的是$Proxy4,下面是內(nèi)容:
// 該類為final類,其繼承了Proxy類,并實(shí)現(xiàn)了被代理接口Foo
public final class $Proxy4 extends Proxy implements Foo {
// 這4個(gè)Method實(shí)例,代表了本類實(shí)現(xiàn)的4個(gè)方法
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// 靜態(tài)代碼塊根據(jù)反射獲取這4個(gè)方法的Method實(shí)例
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("io.github.gozhuyinglong.proxy.Foo").getMethod("ping");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
// 一個(gè)公開(kāi)的構(gòu)造函數(shù),參數(shù)為指定的 InvocationHandler
public $Proxy4(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// Foo接口的實(shí)現(xiàn)方法,最終調(diào)用了 InvocationHandler 中的 invoke 方法
public final String ping(String var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
通過(guò)該文件可以看出:
- 代理類繼承了
Proxy類,其主要目的是為了傳遞InvocationHandler。 - 代理類實(shí)現(xiàn)了被代理的接口Foo,這也是為什么代理類可以直接強(qiáng)轉(zhuǎn)成接口的原因。
- 有一個(gè)公開(kāi)的構(gòu)造函數(shù),參數(shù)為指定的
InvocationHandler,并將參數(shù)傳遞到父類Proxy中。 - 每一個(gè)實(shí)現(xiàn)的方法,都會(huì)調(diào)用
InvocationHandler中的invoke方法,并將代理類本身、Method實(shí)例、入?yún)⑷齻€(gè)參數(shù)進(jìn)行傳遞。這也是為什么調(diào)用代理類中的方法時(shí),總會(huì)分派到InvocationHandler中的invoke方法的原因。
3.2 代理類是如何創(chuàng)建的
我們從Proxy類為我們提供的兩個(gè)靜態(tài)方法開(kāi)始getProxyClass和newProxyInstance。上面已經(jīng)介紹了,這兩個(gè)方法是用來(lái)創(chuàng)建代理類及其實(shí)例的,下面來(lái)看源碼。
3.2.1 getProxyClass 和 newProxyInstance方法


通過(guò)上面源碼可以看出,這兩個(gè)方法最終都會(huì)調(diào)用getProxyClass0方法來(lái)生成代理類的Class對(duì)象。只不過(guò)newProxyInstance方法為我們創(chuàng)建好了代理實(shí)例,而getProxyClass方法需要我們自己創(chuàng)建代理實(shí)例。
3.2.2 getProxyClass0 方法
下面來(lái)看這個(gè)統(tǒng)一的入口:getProxyClass0

從源碼和注解可以看出:
- 代理接口的最多不能超過(guò)65535個(gè)
- 會(huì)先從緩存中獲取代理類,則沒(méi)有再通過(guò)
ProxyClassFactory創(chuàng)建代理類。(代理類會(huì)被緩存一段時(shí)間。)
3.2.3 WeakCache類
這里簡(jiǎn)單介紹一下WeakCache<K, P, V>類,該類主要是為代理類進(jìn)行緩存的。獲取代理類時(shí),會(huì)首先從緩存中獲取,若沒(méi)有會(huì)調(diào)用ProxyClassFactory類進(jìn)行創(chuàng)建,創(chuàng)建好后會(huì)進(jìn)行緩存。

3.2.4 ProxyClassFactory類
ProxyClassFactory是Proxy類的一個(gè)靜態(tài)內(nèi)部類,該類用于生成代理類。下圖是源碼的部分內(nèi)容:

- 代理類的名稱就是在這里定義的,其前綴是
$Proxy,后綴是一個(gè)數(shù)字。 - 調(diào)用
ProxyGenerator.generateProxyClass來(lái)生成指定的代理類。 -
defineClass0方法是一個(gè)native方法,負(fù)責(zé)字節(jié)碼加載的實(shí)現(xiàn),并返回對(duì)應(yīng)的Class對(duì)象。
3.3 原理圖
為了便于記錄,將代理類的生成過(guò)程整理成了一張圖。

源碼分享
完整代碼請(qǐng)?jiān)L問(wèn)我的Github,若對(duì)你有幫助,歡迎給個(gè)?,感謝~~??????
推薦閱讀
關(guān)于作者
| 項(xiàng)目 | 內(nèi)容 |
|---|---|
| 公眾號(hào) | 碼農(nóng)StayUp(ID:AcmenStayUp) |
| 主頁(yè) | https://gozhuyinglong.github.io |
| CSDN | https://blog.csdn.net/gozhuyinglong |
| 掘進(jìn) | https://juejin.cn/user/1239904849494856 |
| Github | https://github.com/gozhuyinglong |
| Gitee | https://gitee.com/gozhuyinglong |