作者简介:大家好,我是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.获取目标方法Method
m3 = 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
进群,大家一起学习,一起进步,一起对抗互联网寒冬