
微信搜索:碼農StayUp
主頁地址:https://gozhuyinglong.github.io
原始碼分享:https://github.com/gozhuyinglong/blog-demos
JDK動態代理是指:代理類實體在程式運行時,由JVM根據反射機制動態的生成,也就是說代理類不是用戶自己定義的,而是由JVM生成的,
由于其原理是通過Java反射機制實作的,所以在學習前,要對反射機制有一定的了解,傳送門:Java反射機制:跟著代碼學反射
下面是本篇講述內容:

1. JDK動態代理的核心類
JDK動態代理有兩大核心類,它們都在Java的反射包下(java.lang.reflect),分別為InvocationHandler介面和Proxy類,
1.1 InvocationHandler介面
代理實體的呼叫處理器需要實作
InvocationHandler介面,并且每個代理實體都有一個關聯的呼叫處理器,當一個方法在代理實體上被呼叫時,這個方法呼叫將被編碼并分派到其呼叫處理器的invoke方法上,
也就是說,我們創建的每一個代理實體都要有一個關聯的InvocationHandler,并且在呼叫代理實體的方法時,會被轉到InvocationHandler的invoke方法上,
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
該invoke方法的作用是:處理代理實體上的方法呼叫并回傳結果,
其有三個引數,分別為:
- proxy:是呼叫該方法的代理實體,
- method:是在代理實體上呼叫的介面方法對應的
Method實體, - args:一個
Object陣列,是在代理實體上的方法呼叫中傳遞的引數值,如果介面方法為無參,則該值為null,
其回傳值為:呼叫代理實體上的方法的回傳值,
1.2 Proxy類
Proxy類提供了創建動態代理類及其實體的靜態方法,該類也是動態代理類的超類,
代理類具有以下屬性:
- 代理類的名稱以 “$Proxy” 開頭,后面跟著一個數字序號,
- 代理類繼承了
Proxy類, - 代理類實作了創建時指定的介面(JDK動態代理是面向介面的),
- 每個代理類都有一個公共建構式,它接受一個引數,即介面
InvocationHandler的實作,用于設定代理實體的呼叫處理器,
Proxy提供了兩個靜態方法,用于獲取代理物件,
1.2.1 getProxyClass
用于獲取代理類的Class物件,再通過呼叫建構式創建代理實體,
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
該方法有兩個引數:
- loader:為類加載器,
- intefaces:為介面的
Class物件陣列,
回傳值為動態代理類的Class物件,
1.2.2 newProxyInstance
用于創建一個代理實體,
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
該方法有三個引數:
- loader:為類加載器,
- interfaces:為介面的
Class物件陣列, - h:指定的呼叫處理器,
回傳值為指定介面的代理類的實體,
1.3 小結
Proxy類主要用來獲取動態代理物件,InvocationHandler介面主要用于方法呼叫的約束與增強,
2. 獲取代理實體的代碼示例
上一章中已經介紹了獲取代理實體的兩個靜態方法,現在通過代碼示例來演示具體實作,
2.1 創建目標介面及其實作類
JDK動態代理是基于介面的,我們創建一個介面及其實作類,
Foo介面:
public interface Foo {
String ping(String name);
}
Foo介面的實作類RealFoo:
public class RealFoo implements Foo {
@Override
public String ping(String name) {
System.out.println("ping");
return "pong";
}
}
2.2 創建一個InvocationHandler
創建一個InvocationHandler介面的實作類MyInvocationHandler,該類的構造方法引數為要代理的目標物件,
invoke方法中的三個引數上面已經介紹過,通過呼叫method的invoke方法來完成方法的呼叫,
這里一時看不懂沒關系,后面原始碼決議章節會進行剖析,
public class MyInvocationHandler implements InvocationHandler {
// 目標物件
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 方式一:通過getProxyClass方法獲取代理實體
具體實作步驟如下:
- 根據類加載器和介面陣列獲取代理類的Class物件
- 過Class物件的構造器創建一個實體(代理類的實體)
- 將代理實體強轉成目標介面Foo(因為代理類實作了目標介面,所以可以強轉),
- 最后使用代理進行方法呼叫,
@Test
public void test1() throws Exception {
Foo foo = new RealFoo();
// 根據類加載器和介面陣列獲取代理類的Class物件
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
// 通過Class物件的構造器創建一個實體(代理類的實體)
Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class)
.newInstance(new MyInvocationHandler(foo));
// 呼叫 ping 方法,并輸出回傳值
String value = fooProxy.ping("楊過");
System.out.println(value);
}
輸出結果:
proxy - class com.sun.proxy.$Proxy4
method - public abstract java.lang.String io.github.gozhuyinglong.proxy.Foo.ping(java.lang.String)
args - [楊過]
ping
pong
通過輸出結果可以看出:
- 代理類的名稱是以
$Proxy開頭的, - 方法實體為代理類呼叫的方法,
- 引數為代理類呼叫方法時傳的引數,
2.4 方式二:通過newProxyInstance方法獲取代理實體
通過這種方法是最簡單的,也是推薦使用的,通過該方法可以直接獲取代理物件,
注:其實該方法后臺實作實際與上面使用getProxyClass方法的程序一樣,
@Test
public void test2() {
Foo foo = new RealFoo();
// 通過類加載器、介面陣列和呼叫處理器,創建代理類的實體
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 通過Lambda運算式簡化實作
其實InvocationHander介面也不用創建一個實作類,可以使用Lambad運算式進行簡化的實作,如下代碼:
@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為我們自動生成的代理類到底是什么樣子的呢?下面我們先來生成一下,再來看里面的構造,
3.1.1 生成$Proxy的.class檔案
JVM默認不創建該.class檔案,需要增加一個啟動引數:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
在IDEA中點擊【Edit Configurations…】,打開 Run/Debug Configurations 配置框,

將上面啟動引數加到【VM options】中,點擊【OK】即可,

再次運行代碼,會在專案中的【com.sun.proxy】目錄中找到這個.class檔案,我這里是“$Proxy4.class”

3.1.2 為什么加上這段啟動引數就能生成$Proxy的位元組碼檔案
在Proxy類中有個ProxyClassFactory靜態內部類,該類主要作用就是生成靜態代理的,
其中有一段代碼ProxyGenerator.generateProxyClass用來生成代理類的.class檔案,

其中變數saveGeneratedFiles便是參考了此啟動引數的值,將該啟動引數配置為true會生成.class檔案,

3.1.3 這個代理類$Proxy到底是什么樣子呢
神秘的面紗即將揭露,前面很多未解之迷在這里可以找到答案!
打開這個$Proxy檔案,我這里生成的是$Proxy4,下面是內容:
// 該類為final類,其繼承了Proxy類,并實作了被代理介面Foo
public final class $Proxy4 extends Proxy implements Foo {
// 這4個Method實體,代表了本類實作的4個方法
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// 靜態代碼塊根據反射獲取這4個方法的Method實體
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());
}
}
// 一個公開的建構式,引數為指定的 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介面的實作方法,最終呼叫了 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);
}
}
}
通過該檔案可以看出:
- 代理類繼承了
Proxy類,其主要目的是為了傳遞InvocationHandler, - 代理類實作了被代理的介面Foo,這也是為什么代理類可以直接強轉成介面的原因,
- 有一個公開的建構式,引數為指定的
InvocationHandler,并將引數傳遞到父類Proxy中, - 每一個實作的方法,都會呼叫
InvocationHandler中的invoke方法,并將代理類本身、Method實體、入參三個引數進行傳遞,這也是為什么呼叫代理類中的方法時,總會分派到InvocationHandler中的invoke方法的原因,
3.2 代理類是如何創建的
我們從Proxy類為我們提供的兩個靜態方法開始getProxyClass和newProxyInstance,上面已經介紹了,這兩個方法是用來創建代理類及其實體的,下面來看原始碼,
3.2.1 getProxyClass 和 newProxyInstance方法


通過上面原始碼可以看出,這兩個方法最終都會呼叫getProxyClass0方法來生成代理類的Class物件,只不過newProxyInstance方法為我們創建好了代理實體,而getProxyClass方法需要我們自己創建代理實體,
3.2.2 getProxyClass0 方法
下面來看這個統一的入口:getProxyClass0

從原始碼和注解可以看出:
- 代理介面的最多不能超過65535個
- 會先從快取中獲取代理類,則沒有再通過
ProxyClassFactory創建代理類,(代理類會被快取一段時間,)
3.2.3 WeakCache類
這里簡單介紹一下WeakCache<K, P, V>類,該類主要是為代理類進行快取的,獲取代理類時,會首先從快取中獲取,若沒有會呼叫ProxyClassFactory類進行創建,創建好后會進行快取,

3.2.4 ProxyClassFactory類
ProxyClassFactory是Proxy類的一個靜態內部類,該類用于生成代理類,下圖是原始碼的部分內容:

- 代理類的名稱就是在這里定義的,其前綴是
$Proxy,后綴是一個數字, - 呼叫
ProxyGenerator.generateProxyClass來生成指定的代理類, defineClass0方法是一個native方法,負責位元組碼加載的實作,并回傳對應的Class物件,
3.3 原理圖
為了便于記錄,將代理類的生成程序整理成了一張圖,

原始碼分享
完整代碼請訪問我的Github,若對你有幫助,歡迎給個?,感謝~~🌹🌹🌹
https://github.com/gozhuyinglong/blog-demos/tree/main/java-source-analysis/src/main/java/io/github/gozhuyinglong/proxy
推薦閱讀
- Java反射機制:跟著代碼學反射
關于作者
| 專案 | 內容 |
|---|---|
| 公眾號 | 碼農StayUp(ID:AcmenStayUp) |
| 主頁 | https://gozhuyinglong.github.io |
| CSDN | https://blog.csdn.net/gozhuyinglong |
| 掘進 | https://juejin.cn/user/1239904849494856 |
| Github | https://github.com/gozhuyinglong |
| Gitee | https://gitee.com/gozhuyinglong |

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/265662.html
標籤:java
上一篇:Java網路編程——TCP通信原理(這個是真簡單哦!!!點進來看看就知道了)
下一篇:普歌-允異團隊-java做小成果
