作者簡介:大家好,我是smart哥,前中興通訊、美團架構師,現某互聯網公司CTO
聯系qq:184480602,加我進群,大家一起學習,一起進步,一起對抗互聯網寒冬
照理說,動態代理經過前面3篇介紹,該講的都已經講完了,再深入下去的意義不是特別大。但看到群里有小伙伴說對InvocationHandler#invoke()方法的參數有些困惑,所以又補了一篇。
關于這三個參數,其實一句話就能講完:
- Object proxy:很遺憾,是代理對象本身,而不是目標對象(不要調用,會無限遞歸,一般不會使用)
- Method method:方法執行器,用來執行方法(有點不好解釋,Method只是一個執行器,傳入目標對象就執行目標對象的方法)
- Obeject[] args:方法參數
上一篇也是這么介紹的,但大家的接受度似乎不是很好,甚至對參數怎么傳進來的感到困惑,所以本篇打算站在Proxy類設計的角度分析三個參數的由來。
當然啦,我還遠不敢說能寫出JDK級別的代碼。本文雖然也嘗試編寫MyProxy類,但它是用來解釋參數由來的,意義并不在于完美復刻JDK Proxy。
山寨Proxy類概覽
先看一下我編寫的山寨MyProxy和MyInvocationHandler吧:
是不是和上面原版的JDK Proxy幾乎一模一樣呢?激動嗎?一起來看看我是怎么設計的(不要細想代碼邏輯,這根本是偽代碼,跑不起來的)。
/*** 山寨Proxy類*/
public static class MyProxy implements java.io.Serializable {protected MyInvocationHandler h;private MyProxy() {}protected MyProxy(MyInvocationHandler h) {Objects.requireNonNull(h);this.h = h;}public static Object newProxyInstance(ClassLoader classLoader,Class<?>[] interfaces,MyInvocationHandler h) throws Exception {// 拷貝一份接口Class(接口可能有多個,所以拷貝的Class也有多個)final Class<?>[] interfaceCls = interfaces.clone();// 這里簡化處理,只取第一個Class<?> copyClazzOfInterface = interfaceCls[0];// 獲取Proxy帶InvocationHandler參數的那個有參構造器Constructor<?> constructor = copyClazzOfInterface.getConstructor(MyInvocationHandler.class);// 創建一個Proxy代理對象,并把InvocationHandler塞到代理對象內部,返回代理對象return constructor.newInstance(h);}}/*** 山寨InvocationHandler接口*/
interface MyInvocationHandler {Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
也就是說,上面的設計思路就是之前分析的這張圖:
目前上面的MyProxy有兩個問題沒解決:
- 返回的代理對象只是Proxy類型的,沒法強轉為目標接口類型
- 返回的代理對象即使能調用接口的同名方法,如何最終調用到它內部的InvocationHandler#invoke()呢
底層原理
上面遺留的兩個問題,其實換種說法就是:
- 怎么讓MyProxy的實例對象變成代理類的對象呢(比如Calculator)?
- InvocationHandler#invoke()怎么調用到目標對象同名方法?
首先我們要明確,MyProxy(JDK Proxy同理)是一個已經寫好的類,一開始就沒有實現Calculator接口,那么它的實例對象肯定是無法強轉為Calculator的。那么,Java是如何解決這個問題的呢?方式很簡單粗暴,因為JVM確確實實在運行時動態構造了代理類,并讓代理類實現了接口,也就是我們經常看到的$Proxy0。
也就是說,我們通常理解的代理對象,并不是JDK Proxy的直接實例對象,而是JDK Proxy的子類$Proxy0的實例對象,而$Proxy0 extends Proxy implements Calculator
由于$Proxy0是運行時的產物,一旦程序停止便會消失,我們需要借助阿里開源的Arthas工具來觀察并驗證。
假設需要代理的接口是:
/*** 需要代理的接口*/
interface Calculator {int add(int a, int b);
}
當我們期望使用Proxy創建代理對象時,JDK會先動態生成一個代理類$Proxy0:
final class $Proxy0 extends Proxy implements InvocationHandlerTest.Calculator {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("com.bravo.demo.InvocationHandlerTest$Calculator").getMethod("add", Integer.TYPE, Integer.TYPE);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final int add(int n, int n2) {try {return (Integer)this.h.invoke(this, m3, new Object[]{n, n2});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}
簡化無關代碼:
// 1.自動實現目標接口,所以代理對象可以轉成Calculator
final class $Proxy0 extends Proxy implements InvocationHandlerTest.Calculator {private static Method m3;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {// 2.獲取目標方法Methodm3 = Class.forName("com.bravo.demo.InvocationHandlerTest$Calculator").getMethod("add", Integer.TYPE, Integer.TYPE);}public final int add(int n, int n2) {// 3.通過InvocationHandler執行方法,現在你能理解invoke()三個參數的含義了嗎?// this:就是$Proxy0的實例,所以是代理對象,不是目標對象return (Integer)this.h.invoke(this, m3, new Object[]{n, n2});}}
最后一個問題是,代理對象$proxy調用add()時,是如何最終調用到目標對象的add()方法的呢?觀察上面的代碼可以發現,代理對象的方法調用都是通過this.h.invoke()橋接過去的,而這個h就是InvocationHandler,在$Proxy的父類Proxy中已經存在,而且會被賦值。
我編寫的MyProxy基本上就是簡化版的JDK Proxy,沒有本質的區別。只不過JVM只認識JDK Proxy,只會給它生成動態代理類,所以我的MyProxy即使模仿到99.99%,也注定少了最關鍵的那一步,最終淪為一段玩具代碼。
希望這篇文章能幫大家更了解JDK動態代理。至于Java是如何自動生成$Proxy代理類的,交給大家另外研究。很多讀者讓我講講CGLib,其實沒啥好講的,就是API使用而已,底層也是自動生成代理類/代理對象,和JDK動態代理很相似,只不過CGLib底層用的是ASM,感興趣可以去百度一下。
作者簡介:大家好,我是smart哥,前中興通訊、美團架構師,現某互聯網公司CTO
進群,大家一起學習,一起進步,一起對抗互聯網寒冬