当前位置: 首页 > news >正文

JDK动态代理:深入解析Java动态代理的核心实现

文章目录

    • 一、动态代理的本质
    • 二、核心实现机制
      • 1. 核心类与职责
      • 2. 代理对象创建流程
    • 三、完整实践示例解析
    • 四、代理类结构解析
    • 四、方法调用全流程
    • 六、关键特性与限制
    • 总结

摘要:本文深入剖析JDK动态代理的实现机制,结合字节码生成与调用转发原理,揭示其如何成为Spring等框架的AOP基石。

前面看Spring AOP的源码,所以对于 JDK 动态代理具体是如何实现的产生了兴趣,想着从源码上了解这个原理的话,也有助于对 spring-aop 模块的理解;


JDK 动态代理其实是 Java 标准库提供的基于接口的代理实现,其核心原理可概括为 运行时动态生成代理类字节码 + 方法调用转发。以下是详细解析:

一、动态代理的本质

动态代理是一种运行时生成代理对象的技术,无需手动编写代理类。JDK动态代理是Java标准库提供的原生方案,其核心能力:

  • 接口代理:仅支持基于接口的代理
  • 无侵入扩展:在方法调用前后插入自定义逻辑(如日志、事务)
  • 运行时字节码生成:动态创建代理类的.class文件

🌟 典型场景:Spring的@Transactional事务管理、MyBatis的Mapper接口实现

二、核心实现机制

1. 核心类与职责

类/接口职责
java.lang.reflect.Proxy入口类,提供newProxyInstance()创建代理对象
InvocationHandler调用处理器,所有代理方法调用均转发到其invoke()方法
ProxyGenerator (内部)动态生成代理类字节码(JDK内部类)

2. 代理对象创建流程

// Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
// 创建代理对象的核心代码
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{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.*/// 1. 动态生成代理类字节码Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}// 2. 获取构造器并实例化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);}
}

关键步骤:

  1. 根据接口数组生成代理类字节码(如$Proxy0.class
  2. 通过类加载器加载字节码
  3. 反射构造代理对象,绑定InvocationHandler

三、完整实践示例解析

一般会使用实现了 InvocationHandler接口 的类作为代理对象的生产工厂类,并且通过持有被代理对象target,来在 invoke() 方法中对被代理对象的目标方法进行增强和调用,这些通过下面这段代码就看懂:

// 我的代理工厂类,实现了InvocationHandler 接口
public class MyProxyFactory implements InvocationHandler {private Object target = null;public Object getInstance(Object target) {this.target = target;return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res = null;System.out.println("前置增强");res = method.invoke(target, args);System.out.println("后置增强");return res;}
}

但代理对象是如何生成的?invoke()方法 又是如何被调用的呢?

带着这两个疑问接着看下面的代码:

// 定义一个 test 的接口
public interface MyInterface {void play();
}/*** 基于JDK代理的标准,该类实现了接口 MyInterface 和接口的 play() 方法,可以作为被代理类*/
public class TargetObject implements MyInterface{@Overridepublic void play() {System.out.println("TargetObject play");}
}

基于上面的可以杯代理的TargetObject 类, 来做下测试,代码如下:

public class ProxyTest {public static void main(String[] args) {TargetObject target = new TargetObject();/*** MyProxyFactory 实现了 InvocationHandler接口,其中的 getInstance()方法就是利用Proxy类生成并返回了target目标对象的代理对象;* MyProxyFactory 持有对 target 的引用,可以在invoke()中对目标方法前置后置做增强处理,并完成对target相应方法的调用。*/MyProxyFactory proxyFactory = new MyProxyFactory();/*** 这个mi就是JDK生成的代理类,即动态生成的代理类$Proxy0的实例,该实例中的方法都持有对invoke()方法的回调,* 所以当调用其方法时,就能够执行invoke()中的增强处理*/MyInterface mi = (MyInterface) proxyFactory.getInstance(target);// 可以看到 mi 的 Class 到底是什么System.out.println(mi.getClass());/*** 这里实际上调用的就是$Proxy0代理类实例对象中对play()方法的实现,* 结合下面的代码可以看到play()方法 通过 super.h.invoke() 完成了对InvocationHandler对象(proxyFactory)中invoke()方法 的回调,* 所以我们才能够通过 invoke()方法实现对 target对象方法的前置后置增强处理*/mi.play();// 总的来说,JDK动态生成的代理类中对 invoke()方法进行了回调,就是在 invoke()方法中完成target目标方法的调用,及前置后置增强,}
}// 运行结果:
class com.sun.proxy.$Proxy0
前置增强
TargetObject play
后置增强

由上面的分析我们有的大致了解到实际生产的代理对象类是class com.sun.proxy.$Proxy0;因为代理类是运行时生成的,运行结束也就看不到了,为了我们方便的看生成的代理类究竟是长啥样, 我们可以借助 JDK 提供的工具类(ProxyGenerator)的 generateProxyClass 方法生成代理类的字节码,并保存为 .class文件。具体实现如下代码:

public class ProxyTest {@Testpublic void generatorSrc() {// 调用 generateProxyClass 方法生成字节码byte[] bytesFile = ProxyGenerator.generateProxyClass("$Proxy0", TargetObject.class.getInterfaces());// 将字节码写入文件String path = System.getProperty("user.dir") + "\\$Proxy0.class";File file = new File(path);try (FileOutputStream fos = new FileOutputStream(file)) {fos.write(bytesFile);fos.flush();} catch (IOException e) {throw new RuntimeException(e);}}
}

接下来我们可以借助Java反编译工具, 或者直接用idea打开$Proxy0.class文件一探究竟。

四、代理类结构解析

打开的$Proxy0.class结构如下:

// 生成的代理类示例(反编译后)
// Proxy 生成的代理类,可以看到,其继承了 Proxy,并且实现了 被代理类的接口MyInterface
public final class $Proxy0 extends Proxy implements MyInterface {// 静态方法引用表private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}// 代理方法实现public final void play() throws  {try {// 关键调用:转发至InvocationHandler// 这个 h 其实就是我们调用 Proxy.newProxyInstance()方法 时传进去的 MyProxyFactory对象(它实现了InvocationHandler接口),// 该对象的 invoke()方法 中实现了对目标对象的目标方法的增强。// 看到这里,利用动态代理实现方法增强的实现原理就很清晰了super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");// 实例化 MyInterface 的 play()方法m3 = Class.forName("com.umzhang.springwebdemo.testproxy.MyInterface").getMethod("play");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

核心设计:

  • 继承Proxy父类,持有InvocationHandler实例
  • 静态块初始化接口方法引用
  • 每个代理方法直接调用InvocationHandler.invoke()

四、方法调用全流程

在这里插入图片描述
关键环节:

  1. 代理方法拦截:代理类方法被调用
  2. 统一转发:调用InvocationHandler.invoke()
  3. 反射执行:通过method.invoke(target, args)调用目标方法
  4. 增强扩展点:在invoke()中可插入前后逻辑

六、关键特性与限制

✅ 核心优势:

  • 动态字节码生成,使用ProxyGenerator动态生成.class文件
  • 类名格式:$ProxyN(N为自增数字)

⚠️ 使用限制

  • 接口依赖:目标类必须实现至少一个接口(无法代理无接口的类)
  • final方法:不能代理final方法(代理类通过继承实现,无法重写final方法)
  • 私有方法:只能代理接口声明的方法(私有方法不会被拦截)

与CGLIB代理对比
在这里插入图片描述

总结

JDK动态代理通过运行时字节码生成 + 方法调用转发两大核心机制,实现了优雅的代理模式:

  1. 技术价值:解耦业务逻辑与横切关注点(如日志、事务);为Spring等框架提供AOP底层支持。
  2. 应用边界:适用于基于接口的代理场景;性能敏感场景需关注反射调用开销(可结合JIT优化)。

JDK 动态代理通过 运行时生成代理类字节码 + 统一方法转发到 InvocationHandler 实现动态代理。其设计优雅地结合了 Java 反射机制与类加载机制,成为 Java 生态中 AOP 和中间件开发的基础技术。尽管存在接口依赖的限制,但在 Spring 等框架的巧妙封装下,仍被广泛应用于企业级开发。
掌握其原理,能更深入理解Java生态中 AOP 编程、RPC 框架、Spring 容器、Mock 测试 等技术的实现本质。


End!

http://www.dtcms.com/a/273279.html

相关文章:

  • qwen3、gemma3 GPRO强化训练案例
  • spring-ai agent概念
  • 6.4 BL2到BL31/BL33的切换
  • Android 13----在framworks层映射一个物理按键
  • C++并发编程-12. 用内存顺序实现内存模型
  • 写《XX顶层设计》和《XX可研报告》区别。
  • MySQL索引:数据库的超级目录
  • 网站文章更新慢影响排名?AI批量写文章技巧分享
  • 综合演练——名片管理系统I
  • Canvas 状态管理 语法糖 canvas.withSave() {}
  • AtCoder Beginner Contest 413
  • 并发编程原理与实战(十六)深入锁的演进,为什么有了synchronized还需要Lock?
  • UECC-UE连接协调的运作方式
  • (一)OpenCV——噪声去除(降噪)
  • React--Fiber 架构
  • 数据库操作核心知识点整理
  • mac m1芯片 安装pd及win10系统
  • 第12讲—一元函数积分学的物理应用
  • 在vscode中安装jupyter
  • 电机电角度与机械角度的个人理解:从蒙圈到理解到放弃
  • 蓝桥云课 矩形切割-Java
  • 快速分页wpf
  • npx cowsay 让动物说话~
  • Java重试+事务的方式优化saveBatch,配置信息修改
  • Flink Exactly Once 和 幂等
  • 【郑大二年级信安小学期】Day9:XSS跨站攻击XSS绕过CSRF漏洞SSRF漏洞
  • 服务器深夜告警?可能是攻击前兆!
  • Unity插件——ABC详解
  • AI驱动的低代码革命:解构与重塑开发范式
  • LeetCode 8. 字符串转换整数 (atoi)