jdk动态代理类由classloader通过defineclass()直接定义字节码,跳过传统文件加载但遵循双亲委派与类生命周期;cglib则生成目标类的子类,依赖asm操作字节码并需规避final限制。

Java 类加载机制对动态代理生成的类的处理,核心在于:它不从磁盘或 JAR 包中读取 .class 文件,而是直接在运行时生成字节码并交给类加载器定义为 Class 对象——整个过程绕过了传统“加载”阶段的文件 I/O,但依然严格遵循类加载的生命周期和双亲委派模型。
动态代理类的字节码是运行时生成的
当调用 Proxy.newProxyInstance() 时,JDK 内部会做以下几件事:
- 根据传入的接口数组,检查是否已缓存对应代理类(以
$ProxyN命名); - 若未缓存,则通过内置的字节码生成逻辑(非 javac 编译,也非 ASM/CGLib 等第三方库),拼装出符合 JVM 规范的 class 字节码;
- 该字节码被封装为
byte[],然后交由当前传入的 ClassLoader 调用其defineClass()方法完成类定义。
类加载器参与但不执行传统“加载”
这里的“加载”不是指从外部路径读取文件,而是指 defineClass() 这一本地方法的调用。它跳过了“查找字节流→校验→解析”的前置步骤,直接进入“链接”阶段(验证、准备、解析)和“初始化”阶段。关键点包括:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 代理类由指定的 ClassLoader(如 AppClassLoader)调用
defineClass()定义,因此该类的 类加载器就是你传入的那个,不是 Bootstrap 或 Ext; - 虽然没走 findClass(),但仍遵守双亲委派:如果某个框架(如 Spring AOP)使用自定义 ClassLoader 创建代理,它仍会先委托父加载器尝试加载已有类(比如被代理的接口),再自己 define 代理类;
- 生成的代理类是 public final,只实现接口,不继承任何类,因此不会触发父类初始化逻辑(除非接口含 static 块且首次使用)。
代理类在方法区与堆中的存在形式
一旦 defineClass 成功,JVM 就会在方法区创建类型数据结构,并在堆中生成一个 java.lang.Class 实例,代表这个动态代理类。后续所有对该代理类的操作(如 newInstance、反射调用方法)都基于这个 Class 对象。它和普通编译生成的类在 JVM 内部地位完全一致:
- 可被
Class.forName()查找(需知道完整类名,如$Proxy0,但一般不建议硬编码); - 能参与多态、类型转换(如强制转为被代理接口);
- 卸载条件相同:所在 ClassLoader 被回收,且无该类的任何实例或引用。
和 CGLib 等字节码增强工具的区别
JDK 动态代理只支持接口,所以生成的类本质是“实现接口的空壳”,所有方法调用最终都落入 InvocationHandler.invoke();而 CGLib 生成的是目标类的子类,需要操作类继承关系,因此必须用 Unsafe.defineAnonymousClass() 或自定义 ClassLoader 的 defineClass() 来避开 final 类限制。两者都依赖类加载器完成最后一步,但字节码来源和生成策略不同。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










