本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力
动态代理的简要说明
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制。实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了反射机制。还有其他的实现方式,比如利用更高性能的字节码操作机制,类似 ASM、cglib、Javassist 等。
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class)。通过代理可以让调用者与实现者之间解耦。比如进行 RPC 调用,框架内部的寻址、序列化、反序列化等。每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。InvocationHandler这个接口的唯一一个方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throwsThrowable 这个方法接收三个参数和返回一个Object类型,它们分别代表的意思如下:
proxy:所代理的那个真实对象
method:要调用真实对象的方法的Method对象
args:调用真实对象某个方法时接受的参数
返回的Object是指真实对象方法的返回类型
简单对比下两种方式各自优势。
JDK Proxy 的优势:
最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
代码实现简单。
基于类似 cglib 框架的优势:
有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似 cglib 动态代理就没有这种限制。
实例
看JDK动态代理的一个简单例子。下面只是加了一句 print。
public class DemoDynamicProxy{
public static void main(String[] args){
JuejinImpl juejin = new JuejinImpl();
MyInvocationHandler handler = new MyInvocationHandler(juejin);
// 构造代码实例
Juejin proxyjuejin = (Juejin) Proxy.newProxyInstance(JuejinImpl.class.getClassLoader(), JuejinImpl.class.getInterfaces(), handler);
// 调用代理方法
proxyjuejin.sayJuejin();
}
}
interface Juejin{
void sayJuejin();
}
class JuejinImpl implements Juejin{
@Override
public void saJuejin(){
System.out.println("Hello Juejin");
}
}
class MyInvocationHandler implements InvocationHandler{
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Invoking saJuejin");
Object result = method.invoke(target, args);
return result;
}
}
上面的 JDK Proxy 例子,非常简单地实现了动态代理的构建和代理操作。首先,实现对应的 InvocationHandler;然后,以接口 Juejin 为纽带,为被调用目标构建代理对象,进而应用程序就可以使用代理对象间接运行调用目标的逻辑,代理为应用插入额外逻辑(这里是 println)提供了便利的入口。
为什么继承InvocationHandler接口和持有委托类引用的RoommateInvocationHandler调用来自Hungry接口的callLunch()方法时可以调用到委托类对callLunch()的逻辑实现呢,看看它的背后原理:
动态代理的实现原理
从Proxy.newProxyInstance() 入手,逐步分析 InvocationHandler 如何建立代理实例和委托实例的关联:
public static Object newProxyInstance(ClassLoader loader , Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//InvocationHandler必须非空,说明是个重要角色
Objects.requireNonNull(h);
//获取委托类的接口
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
* 核心:通过类加载器和委托类接口,在内存中查找出或者生成指定的代理类
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
* 利用指定的invocation handler调用它的构造器方法,构建代理类的实例返回
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
需要注意的是cl 这个实例创建过程:在上面示例代码main函数的后面接着补充。利用ProxyGenerator.generateProxyClass生成这个动态生成的类文件,写入了指定路径的class文件内,Proxy0 。
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Roommate.class.getInterfaces());
String filePath = "C:\\Users\\ODM\\Desktop\\RoommateProxy.class";
try(FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(classFile);
fos.flush();
}catch (IOException e){
e.printStackTrace();
System.out.println("error:写入文件");
}
使用反编译工具,这个$Proxy0类,实现了Proxy类,继承了和委托类相同的接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy_test.Hungry;
public final class $Proxy0 extends Proxy implements Hungry{
private static Method m1;
private static Method m3; //由下方静态代码块得知,m3代表callLunch()这一个方法
private static Method m2;
private static Method m0;
/*
* 父类Proxy的构造器,其中 h 属性为 InvocationHandler引用
* protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
*/
public $Proxy0(InvocationHandler paramInvocationHandler) throws {
super(paramInvocationHandler);
}
//可供外界调用,方法名与委托类实现接口的方法相同,利用 InvocationHandler调用invoke
public final void callLunch() throws {
try{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError){
throw localError;
}
catch (Throwable localThrowable){
throw new UndeclaredThrowableException(localThrowable);
}
}
public final boolean equals(Object paramObject) throws {}
public final String toString() throws {...}
public final int hashCode() throws {...}
static{
try{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("proxy_test.Hungry").getMethod("callLunch", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException){
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException){
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
从着段源码可以看出:$Proxy0 ,在构建这个类时,会调用了父类Proxy的构造方法,将InvocationHandler引用传递给了父类Proxy的 h 属性,于是在外界使用代理实例调用了callLunch()这个方法时,就会来到this.h.invoke(this, m3, null); 由于h属性其实是InvocationHandler引用,调用了它的invoke,也就导致了上面示例代码中的RoommateInvocationHandler类的重写过的invoke方法也就被调用了,RoommateInvocationHandler也持有委托类的引用,所以委托类的方法也被调用起来了。
Java的继承机制是单继承,多接口。代理类因为必须要继承Proxy类,所以java的动态代理只能对接口进行代理,无法对一个class类进行动态代理。
动态代理原理总结
InvocationHandler类似中间类,中间类构建时持有了委托对象,所以可以在它的invoke方法中调用了委托对象实现接口的具体方法。当外部调用这个InvocationHandler的invoke方法时,对 invoke 的调用最终都转为对委托对象的方法调用。
创建明面上负责代理的代理实例时,在内存中动态生成的类不但继承了Proxy,也实现了与委托对象相同的接口,因此代理实例可以调用此接口的方法,然后通过持有的中介类对象来调用中介类对象的invoke方法,最终达到代理实例执行了委托者的方法。