在前几篇文章中,我们通过指出静态代理的不足及弊端,引申出了动态代理,然后一步步的对代码进行优化。那么在jdk中的动态代理究竟是什么样子呢?本篇我们就来使用jdk的动态代理,并在使用的过程中比较一下和我们实现的动态代理有哪些区别。
首先我们要自定义对方法的增强方式,即实现jdk提供的InvocationHandler接口:
package cn.rain.design.proxy.demo4;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LogHandler implements InvocationHandler {
private Object target;
// 创建实例时要拿到代理对象
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("logger is start...");
// result即方法调用的返回值
Object result = method.invoke(target);
System.out.println("logger is end...");
// 返回方法调用的结果,因为我们知道move方法返回值是void,因此这里的result肯定是null
return result;
}
}
第三个参数是方法入参,在前边的实现中为了简单起见,我们为被代理对象Movable编写的方法中入参和返回值均为空。在后边的分析中我不对这个细节做过多说明,有兴趣的同学可以按照我们前边的思路将带参数的形式实现一遍,这样可以加深印象。
第二个参数method是我们在生成的代理对象$Proxy.java中传过来的method对象,如图:
第一个参数Object proxy是什么呢?
通过命名大家应该也猜到了,这个参数其实就是代理对象$Proxy,它是用户获取到代理对象后,当调用代理对象的方法时,将代理对象自身传递给了InvocationHandler。听起来有点抽象,我们试着用代码还原一下,对以下两个地方进行修改:
修改后,我们再次生成$Proxy.java就是这个样子:
当我们通过MyProxy.newProxyInstance方法获取到代理对象后(也就是$Proxy的实例),调用代理对象的方法时,就会通过h.invoke(this, m)将自身的实例传递给用户。
那么将代理对象proxy传递给invocationHandler有什么用呢?很遗憾,我翻了很多资料也没有找到让我信服的答案,网上有很多文章说是可以在实现InvocationHandler.invoke()的时候将它return回去,以便可以对代理对象反复调用。这明显是没有意义的,因为通过上边的代码我们可以知道,其实传递给InvocationHandler的代理对象proxy和MyProxy.newProxyInstance()获取到的代理对象是一样的,所以根本没有必要返回来。我个人的理解它可能是设计上的缺陷,如果不是的话那它必然是提供给用户在实现InvocationHandler.invoke时候使用,但是具体怎么用,我实在是没有找到很好的例子。我也翻了下jdk中对于proxy的用法,如图:
它除了做前置判断外也没有什么具体的使用,如果大家有研究过它的话,欢迎留言交流。
好了说回正题,我们实现完jdk的InvocationHandler后,再来看一下jdk用于生成代理对象实例的java.lang.reflect.Proxy类:
第一个参数是生成代理对象所用到的类加载器,我们直接写在代码里了;第二个参数传递的是一个Class数组,这很好理解,因为被代理对象可能实现了多个接口,因此代理对象也要实现多个接口去代理所有接口中的方法。
另外需要重点说明的是,这里要传递的必须是被代理对象所实现的接口(如果实现了多个接口可以只传部分接口,我们传入了哪些接口,生成的代理对象就会实现哪些接口),但是如果被代理对象没有实现接口而是继承了某个类,那么它是不能被jdk的动态代理所代理的。也就是说,jdk动态代理只能代理接口,不能代理类。
为什么jdk的动态代理只能代理接口呢,说这个问题之前我们要简单了解一下jdk是怎么生成代理对象的,以及它生成的代理对象和我们的有哪些区别。jdk没有像我们实现的那样去拼接$Proxy.java的字符串,它跳过了这一步,用byte[ ]直接将$Proxy.class字节码文件写到了磁盘上,然后进行编译、创建实例,用完之后再将字节码文件从磁盘上删除。我们可以通过一些手段获取到这个字节码文件(参考 https://blog.csdn.net/qq_34436819/article/details/101310221),对该字节码反编译后:
package com.sun.proxy;
import cn.rain.design.proxy.demo1.model.Movable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Movable {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(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 void move() throws {
try {
return super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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);
}
}
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);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("cn.rain.design.proxy.demo1.model.Movable").getMethod("move");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
列举jdk的代理对象和我们代理对象的区别:
而jdk的动态代理只能代理接口不能代理类正是由于代理对象继承了Proxy,由于Java不允许多继承,因此它无法再去继承一个类。
那么jdk为什么要这么设计呢?像我们实现的那样不继承MyProxy不也是可以的吗?我想jdk这么做的原因至少有以下两点:
至此,我们比较了jdk动态代理和我们的动态代理的所有区别,最后我们使用jdk的Proxy生成Movable的代理对象:
package cn.rain.design.proxy.demo4;
import cn.rain.design.proxy.demo1.model.Car;
import cn.rain.design.proxy.demo1.model.Movable;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
// 创建需要被代理的对象
Car car = new Car();
// 创建InvocationHandler实例
LogHandler h = new LogHandler(car);
// 获取代理对象
Movable m = (Movable) Proxy.newProxyInstance(car.getClass().getClassLoader(), car.getClass().getInterfaces(), h);
// 通过代理对象调用move方法进行方法增强
m.move();
}
}
运行结果:
logger is start...
Car moving....
logger is end...
本系列完。