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

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

在这里插入图片描述

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.&dollar;Proxy0

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

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

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

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

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

08、文末总结

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

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

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

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

相关文章
|
3月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
7天前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
25 4
|
28天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
24 0
[Java]静态代理与动态代理(基于JDK1.8)
|
1月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
21 1
|
3月前
|
Java API 开发者
Jdk动态代理为啥不能代理Class?
该文章主要介绍了JDK动态代理的原理以及为何JDK动态代理不能代理Class。
Jdk动态代理为啥不能代理Class?
|
3月前
|
Java
JDK序列化原理问题之Hessian框架不支持writeObject/readObject方法如何解决
JDK序列化原理问题之Hessian框架不支持writeObject/readObject方法如何解决
|
3月前
|
自然语言处理 JavaScript 前端开发
JDK序列化原理问题之FuryJDK序列化性能问题的如何解决
JDK序列化原理问题之FuryJDK序列化性能问题的如何解决
|
3月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
230 0
|
3月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
188 0
|
3月前
|
缓存 Java
JDK序列化原理问题之Fury如何实现与JDK序列化100%兼容的如何解决
JDK序列化原理问题之Fury如何实现与JDK序列化100%兼容的如何解决
下一篇
无影云桌面