作者简介:大家好,我是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
进群,大家一起学习,一起进步,一起对抗互联网寒冬