一直在使用JDK动态代理, 不明白原理如何实现?

·  阅读 781

01、前言

本来动态代理知识点并不在最近文章列表中, 但是在 mybatis 注册 mapper 接口使用到了, 知其然知其所以然

本篇文章是围绕 JDK 动态代理来进行说明, 需要读者掌握基本的反射、类加载器相关知识

02、动态代理分类

动态代理属于是静态代理设计模式的一种扩展, 常见的有三种实现方式, 分别是

  • JDK 动态代理
  • JAVASSIST 动态代理
  • 基于 ASM 封装的 CGLIB

03、JDK 动态代理流程

壹. 运行时为接口创建代理类的字节码文件

贰. 通过类加载器将.class 字节码加载到内存

叁. 创建代理类的实例对象, 执行被代理类的目标方法

04、代码演练

4.1 设计背景

我想开发一个新增服务, 需求是我需要在新增完成之后, 发送一个消息通知, 这里使用 JDK 动态代理

4.2 创建接口以及对应实现

创建 AddService 接口

public interface AddService {
    boolean add(String obj);
}
复制代码

创建 AddService 接口对应实现类

public class AddServiceImpl implements AddService {
    @Override
    public boolean add(Object obj) {
        System.out.println("  >>> 新增元素 :: " + obj.toString());
        return true;
    }
}
复制代码

创建 InvocationHandler 实现类, 是动态代理中的调用处理器

public class AddServiceInvocationHandler implements InvocationHandler {

    private AddService addService;

    public AddServiceInvocationHandler(AddService addService) {
        this.addService = addService;
    }

    /**
     * 通过生成的动态代理类, 调用真正被代理类的执行方法
     *
     * @param proxy  动态代理生成后的代理类
     * @param method 被代理类的方法, 相当于例子中add(Object obj)方法
     * @param args   调用方法入参列表, 相当于例子中add(Object obj)的参数
     * @return       执行add(Object obj)的返回参数
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // result 是方法执行返回值 对应 add(Object obj) 的返回值
        // 这里留下个问题, 为什么要代理 addService 而不是 proxy
        Object invoke = method.invoke(addService, args);
        System.out.println("  >>> 发送消息通知, 执行方法名称 :: " + method.getName());
        return invoke;
    }
}
复制代码

创建 Proxy 测试类, 测试动态代理方法

public class ProxyTest {
    public static void main(String[] args) {
        // 将 Proxy.newProxyInstance 生成的动态代理类存放到磁盘中
        // 默认生成路径 com.sun.proxy 包下
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        AddService addService = new AddServiceImpl();
        AddServiceInvocationHandler handler = new AddServiceInvocationHandler(addService);
        // 这里的类加载器为 AppClassLoader, 不明白的可以看下类加载器相关知识
        AddService addServiceProxy = (AddService) Proxy
                .newProxyInstance(addService.getClass().getClassLoader(), addService.getClass().getInterfaces(), handler);
        boolean isSuccess = addServiceProxy.add("麻小花");
        System.out.println("  >>> 动态代理执行 add 方法返回值为 :: " + isSuccess);
        /**
         * 运行结果:
         * >>> 新增元素 :: 麻小花
         * >>> 发送消息通知方法名称 :: add
         * >>> 动态代理执行 add 方法返回值为 :: true
         */
    }
}
复制代码

05、源码解析

5.1 动态代理类生成解析

首先看一下 Proxy.newProxyInstance 方法做了什么操作

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
        throws IllegalArgumentException {
    // 判断InvocationHandler是否为空, 为空抛出异常
    Objects.requireNonNull(h);
    // 接口复制
    final Class<?>[] intfs = interfaces.clone();
    xxx...
    // ❗️存在缓存返回或生成指定的动态代理类
    Class<?> cl = getProxyClass0(loader, intfs);
    try {
        // 省略代码
        // xxx...
        // 获取以InvocationHandler作为参数的构造方法
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        // 省略代码
        // xxx...
        // 创建动态代理类对象实例, 有参构造方法参数为InvocationHandler
        return cons.newInstance(new Object[]{h});
    } catch xxx...
}
复制代码

看一下 getProxyClass0() 是如何生成动态代理的 class 对象的

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    // 65535 是JVM规定的继承接口数量最大值
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // 如果存在指定接口的类加载器定义的代理类缓存, 直接返回, 无则创建
    return proxyClassCache.get(loader, interfaces);
}
复制代码

proxyClassCache 是存储动态代理类的缓存变量, 定义在 Proxy 类中, 这里主要关注 ProxyClassFactory 是如何创建动态代理类即可

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new Proxy.KeyFactory(), new Proxy.ProxyClassFactory());
复制代码

ProxyClassFactory 是位于 Proxy 类中的静态类, 实现了 BiFunction 函数式接口中 apply 方法

如果待生成的动态代理类不存在于 WeakCache, 那么便调用 apply 方法进行创建

get 方法就不解读了, 直接看生成动态代理类的 apply 方法内部实现

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    // 动态代理类的前缀名称
    private static final String proxyClassNamePrefix = "$Proxy";
    // 用于生成代理类的数字名称
    private static final AtomicLong nextUniqueNumber = new AtomicLong();
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    // 省略校验代理接口的代码...
    // ...
    // 非 public 接口, 生成代理类的包名
    String proxyPkg = null;
    for (Class<?> intf : interfaces) {
        int flags = intf.getModifiers();
        if (!Modifier.isPublic(flags)) {
            accessFlags = Modifier.FINAL;
            String name = intf.getName();
            int n = name.lastIndexOf('.');
            String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
            if (proxyPkg == null) {
                proxyPkg = pkg;
            } else if (!pkg.equals(proxyPkg)) {
                throw new IllegalArgumentException(
                    "non-public interfaces from different packages");
            }
        }
    }
    // 如果代理公共接口, 包名默认com.sun.proxy
    if (proxyPkg == null) {
        // if no non-public proxy interfaces, use com.sun.proxy package
        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }
    // 获取报名计数
    long num = nextUniqueNumber.getAndIncrement();
    // 获取包名, 默认全限名称为 com.sun.proxy.$Proxy0, 依次递增 com.sun.proxy.$Proxy1...
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    // 真正的生成代理类的字节码
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
    try {
        // 根据二进制字节码返回相应的Class实例
        return defineClass0(loader, proxyName,
                proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        throw new IllegalArgumentException(e.toString());
    }
}
复制代码

generateProxyClass 方法是 static 关键字修饰的, 位于 sun.misc 包下, 所以在 JDK 源码中无法看到具体实现细节, 从网上找了段反编译的代码

public static byte[] generateProxyClass(final String var0, Class[] var1) {
    ProxyGenerator var2 = new ProxyGenerator(var0, var1);
    // 生成代理类的字节码
    final byte[] var3 = var2.generateClassFile();
    // 根据参数配置, 决定是否把生成的字节码(.class文件)保存到本地磁盘
    // 这也就证明了测试程序为什么设置要设置全局变量
    if(saveGeneratedFiles) {
        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {
                try {
                    FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
                    // 将文件写入磁盘
                    var1.write(var3);
                    // 关闭流
                    var1.close();
                    return null;
                } catch (IOException var2) {
                    throw new InternalError("I/O exception saving generated file: " + var2);
                }
            }
        });
    }
    return var3;
}
复制代码

5.2 生成的动态代理类

在测试程序中设置了全局配置变量 saveGeneratedFiles 为 true, 将生成的动态代理类保存到了磁盘中

使用 JD-GUI 反编译.class 文件

package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.ibatis.autoconstructor.mytest.proxy.AddService;

public final class $Proxy0 extends Proxy implements AddService {
  // equals 方法
  private static Method m1;
  // toString 方法
  private static Method m2;
  // 对应 AddService 的 add 方法
  private static Method m3;
  // hashCode 方法
  private static Method m0;

  // 将Handler作为有参构造的参数赋值父类Proxy
  public $Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  // 重写 equals 方法
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    }
  }
  // 重写 toString 方法
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    }
  }
  // 重写 add 方法
  // ❗️add 方法真正执行的是 InvocationHandler 对象的 invoke 方法
  // ❗️参数分别是代理对象本身 Proxy, 执行方法 Method, 参数列表 Object[]
  public final boolean add(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m3, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    }
  }
  // 重写 hashCode 方法
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    }
  }

  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("org.apache.ibatis.autoconstructor.mytest.proxy.AddService").getMethod("add", new Class[] { Class.forName("java.lang.Object") });
      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());
    }
  }
}
复制代码

通过编译后的动态代理类得知

在生成的动态代理类 (示例代码中的 addServiceProxy 对象) 调用被代理类方法 (示例代码中的 add(Object obj)) 时, 都会由自定义的 InvocationHandler 进行 invoke 调用, 相当于做了一层转发作用

对比静态代理的区别, 静态代理中使用对象对被代理方法进行调用, 动态代理统一由 InvocationHandler 进行方法反射调用

类似 aop 中的前置通知和后置通知, 只要在 method.invoke() 调用前后做出处理即可实现对应功能

06、JDK 动态代理特点

JDK 代理必须是接口并且要有实现类

使用 Proxy 创建动态代理类时需要提供类加载器、实现的接口数组、自定义 InvocationHandler 对象作为参数

生成的动态代理类重写了 Object 类中的三大基本方法

使用静态代码块来初始化接口中方法的 Method 对象, 包含被代理类的方法以及 Object 的三个方法

07、问题列表

7.1 Proxy

壹. 为什么只能代理接口, 不能直接代理指定类

在反编译了生成的动态代理类中看出, 继承了 Proxy 对象, 由于 Java 不支持多继承, 所以不能代理类

贰. 为什么要重写 Object 类的三个方法

这里假设一下, 如果动态代理类不重写 Object 的三个方法, 而 AddServiceImpl 重写了 Object 的 equals、toString 与 hashCode

那么动态代理类调用的还是 Object 的三个方法, 就无法调用到被代理重写的方法

叁. 为什么动态代理类要继承 Proxy

没有找到很好的资料证明, 可能是为了判断一个类是否为动态代理类, 另外也节省了一些内存开销

有明确答案和自己想法的可以通过留言区回复

7.2 InvocationHandler

壹. invoker 方法中第一个参数 Proxy 是什么

invoker 方法中 Proxy 就是生成的动态代理对象, 如果打印 proxy, 那么就是上文提到的 com.sun.proxy.$Proxy0

同时也可以将 proxy 当作返回值返回进行连续调用, 这也是网上比较多的说法

贰. method.invoke(proxy,args) 这么写有什么结果

这是我在看动态代理时, 被绕着的一个点

结果就是会循环调取 method 方法, 如果是 proxy 参数执行

动态代理内部还是调用到自己本身, 最终导致死循环栈溢出

08、文末总结

这一篇只是分析了 JDK 动态代理的主要流程, 大概在整体源码的 60% 左右

有想法的读者可以去看下 Proxy 的动态代理缓存是如何实现的, 以及动态代理类是如何进行 GC 回收等知识点

本来这周定了两篇文章的标准, 最终也是因为项目比较忙, 完成了一篇

希望各位不论工作多么忙碌, 不要忘记学习

本文使用 mdnice 排版

分类:
后端
标签:
分类:
后端
标签: