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 {@Overridepublic 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*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// result 是方法执行返回值 对应 add(Object obj) 的返回值// 这里留下个问题, 为什么要代理 addService 而不是 proxyObject 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...// 创建动态代理类对象实例, 有参构造方法参数为InvocationHandlerreturn 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 ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>> {// 动态代理类的前缀名称private static final String proxyClassNamePrefix = "$Proxy";// 用于生成代理类的数字名称private static final AtomicLong nextUniqueNumber = new AtomicLong();@Overridepublic 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.proxyif (proxyPkg == null) {// if no non-public proxy interfaces, use com.sun.proxy packageproxyPkg = 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作为有参构造的参数赋值父类Proxypublic $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 回收等知识点

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

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

定时器的实现原理 不消耗cpu_一直在使用JDK动态代理, 不明白原理如何实现?相关推荐

  1. 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理

    动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...

  2. 细说JDK动态代理的实现原理

    关于JDK的动态代理,最为人熟知的可能要数Spring AOP的实现,默认情况下,Spring AOP的实现对于接口来说就是使用的JDK的动态代理来实现的,而对于类的代理使用CGLIB来实现.那么,什 ...

  3. (转)细说JDK动态代理的实现原理

    原文:http://blog.csdn.net/mhmyqn/article/details/48474815 关于JDK的动态代理,最为人熟知的可能要数Spring AOP的实现,默认情况下,Spr ...

  4. JDK动态代理的实现原理

    学习JDK动态代理,从源码层次来理解其实现原理参考:http://blog.csdn.net/jiankunking/article/details/52143504 转载于:https://www. ...

  5. JDK动态代理实现与原理分析

    1.何为代理 代理在我们的日常生活中,就有很多体现,房屋租赁代理,校园辅导班招生代理,化妆产品销售代理等.为什么我们要找代理呢,代理是专业的,是方便的.例如我们买一件化妆品,或买一本书,肯定是不能去找 ...

  6. jdk动态代理实例和cglib动态代理实例_CGLib 动态代理 原理解析

    JDK 动态代理实现与原理 首先来看一段CGLib代理的测试代码(MethodInterceptor的测试, 其他类型这里不做展开了). Util类的代码在后面给出的码云片段中 public 下面的输 ...

  7. 高级JAVA - 动态代理的实现原理和源码分析

    在之前的一篇文章中 , 我们简单了解了一下代理模式(JAVA设计模式 - 代理模式) , 本篇我们来学习一下动态代理的实现原理 , 以及源码是怎样的 . JDK动态代理的主要实现步骤如下 : 1 . ...

  8. JDK动态代理实现原理详解(源码分析)

    无论是静态代理,还是Cglib动态代理,都比较容易理解,本文就通过进入源码的方式来看看JDK动态代理的实现原理进行分析 要了解动态代理的可以参考另一篇文章,有详细介绍,这里仅仅对JDK动态代理做源码分 ...

  9. 深入理解JDK动态代理原理,使用javassist动手写一个动态代理框架

    文章目录 系列文章索引 一.动手实现一个动态代理框架 1.初识javassist 2.使用javassist实现一个动态代理框架 二.JDK动态代理 1.编码实现 2.基本原理 (1)getProxy ...

最新文章

  1. 支付宝的架构到底有多牛逼!还没看完我就跪了!
  2. 想做网络工程师不?最好学下Linux
  3. 利用Keras构建自动编码器
  4. 【Scratch】青少年蓝桥杯_每日一题_4.13_猫捉老鼠
  5. GDCM:gdcm::Object的测试程序
  6. 另一种Background job的 debug方法
  7. 英文版opensuse 12.2安装中文输入法ibus
  8. 使用Apache Camel进行负载平衡
  9. 第 8 章 MybatisPlus 扩展
  10. Flutter mixin用法详解
  11. Matlab-基于模型不确定补偿的RBF网络机器人自适应控制仿真
  12. 关联分析中FPGrowth算法原理及实战
  13. 树莓派 USB摄像头 实现网络监控( MJPG-Streamer)
  14. 互联网寒冬下,为什么就你被裁了?
  15. JAVA SE — Day 18
  16. 浅谈系统实现层面稳定性保障
  17. 数学速算法_孩子数学计算老出错?复习阶段,家长赶紧和孩子一起找准原因!...
  18. 【面霸系列 - 3】初级java如何挖掘自身的优势
  19. solidity Error:linearization of inherintance graph impossable
  20. 记录安装python+appium+模拟器的自动化

热门文章

  1. Postgres用returning实现mysql的last_insert_id
  2. 优秀java程序员-利器-eclipse-01 快捷键
  3. Gene Ontology (GO) 简介
  4. Checkbox与RadioGroup的使用方法
  5. 用JAVA Excel API 实现对Excel表格的读写更新 (转)
  6. 【论文写作】学位论文写作过程中应注意的7个问题,非常实用
  7. dump的文件 查看pg_【PG备份恢复】pg_dump命令测试
  8. pb dw graph增加series_如何快速增加tiktok视频的播放量
  9. python如何连接sql server数据库_Python连接SQLServer数据库
  10. Python:PDF文件转图像