Java动态代理在运行时拼接字节码并由ClassLoader.defineClass()直接定义类,跳过磁盘和编译,但仍遵循类生命周期与双亲委派;生成的$Proxy0类实现接口、转发调用至InvocationHandler,加载后与普通类无异。

Java 动态代理不是靠编译器提前生成 .class 文件,而是运行时拼出字节码、直接交给类加载器定义成 Class 对象——整个过程跳过磁盘读取,但依然走完类的生命周期和双亲委派。
字节码是实时拼出来的,不是编译来的
JDK 动态代理(Proxy.newProxyInstance())内部不调用 javac,也不依赖 ASM 或 Javassist。它根据传入的接口列表,用硬编码逻辑“拼接”出符合 JVM 规范的 class 字节码:
- 类名固定格式,如
$Proxy0,包名默认为空或由 ClassLoader 决定; - 生成的类是
public final,只实现指定接口,不继承任何父类; - 每个接口方法都对应一个同签名的方法体,内部统一转发给
InvocationHandler.invoke(); - 字节码最终封装为
byte[],不落地到文件系统。
类加载器参与定义,但绕过传统“加载”阶段
这个 byte[] 不是通过 findClass() 从路径里找来的,而是直接传给 ClassLoader.defineClass():
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
-
defineClass()是本地方法,它跳过“查找字节流”环节,直接进入链接(验证、准备、解析)和初始化; - 代理类的类加载器就是你传给
newProxyInstance()的那个,比如AppClassLoader; - 双亲委派仍生效:加载前会先委托父加载器检查是否已有同名类(比如已加载的接口),再自己定义代理类;
- 若使用自定义 ClassLoader(如 Spring 的 ContextClassLoader),它也按同样规则工作。
生成后和普通类完全一样
一旦 defineClass() 成功返回,JVM 就在方法区建立类型数据,在堆中创建对应的 Class> 实例:
- 该 Class 可被反射访问,例如
Class.forName("$Proxy0")(但类名不可预测,不建议硬编码); - 代理对象可强转为任意被代理接口,支持多态和 instanceof 判断;
- 卸载条件也一致:所在 ClassLoader 被回收 + 无该类的实例或静态引用;
- 它不触发父类初始化(因为没父类),但若接口含
static {}块且首次使用,会触发接口初始化。
CGLIB 的路径略有不同
CGLIB 不基于接口,而是用 ASM 操作字节码,动态生成目标类的子类:
- 需要规避
final类或方法限制(否则生成失败); - 同样调用
defineClass()完成加载,类加载器行为与 JDK 代理一致; - 生成的类有父类,因此可能触发目标类的静态初始化块(如果首次主动使用)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










