java动态代理机制

235 阅读5分钟

本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力

动态代理的简要说明

动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制。实现动态代理的方式很多,比如 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是代理类在系统内部的编号,在示例代码只生成了一个代理类所以编号是Proxy0 是 代理类在系统内部的编号,在示例代码只生成了一个代理类所以编号是 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方法,最终达到代理实例执行了委托者的方法。