CGLIB(Code Generation Library)是一个运行期生成字节码的代码生成库,能在不实现接口的情况下,为一个类动态创建子类代理。Spring 内部大量依赖它做方法拦截(method interception)——无论是 AOP 代理还是 @Configuration 类的单例保证,背后用的都是 Spring 自己维护的一份 CGLIB fork(org.springframework.cglib),而不是外部独立发布的 cglib.jar。本文以 Spring 里的 CGLIB 为主线;原始独立 CGLIB 项目的来历、维护现状作为背景信息放在文末。
为什么需要 CGLIB:JDK 动态代理的限制
JDK 自 1.3 起自带动态代理(java.lang.reflect.Proxy),使用简单,但有一个硬性限制:被代理的类必须实现一个或多个接口,代理对象本质上是这些接口的另一份实现。如果要代理一个没有实现任何接口的普通类,JDK 动态代理就无能为力了——这正是 CGLIB 存在的原因。
CGLIB 的做法是:在运行期通过字节码处理框架 ASM,动态生成目标类的一个子类,重写其中的非 final 方法,在方法体里插入拦截逻辑,再调用原方法。因为是继承实现,所以不要求目标类实现接口,但也带来限制:类和方法不能是 final(无法被继承和重写),构造函数会被调用(生成的子类需要调用父类构造器)。
CGLIB 和 ASM 的分工:CGLIB 只关心「给目标类生成一个什么样的子类代理」这个业务语义层面的问题——该重写哪些方法、方法体里怎么织入拦截逻辑、生成的类怎么加载成 Class 对象并实例化;至于「怎么把这份子类描述翻译成合法的 .class 字节码」,全部委托给 ASM 完成。可以理解成:CGLIB 决定生成一个什么样的子类,ASM 负责把这个子类真正写成字节码——CGLIB 是构建在 ASM 之上、面向「动态生成子类代理」这个具体场景的应用层库,ASM 才是真正操作字节码指令的底层工具。
| JDK 动态代理 | CGLIB | |
|---|---|---|
| 实现方式 | 实现目标接口 | 生成目标类的子类 |
| 前提条件 | 目标类必须实现接口 | 目标类和方法不能是 final |
| 底层机制 | 反射(InvocationHandler) |
ASM 生成字节码 |
| 典型使用方 | Spring AOP(默认,接口存在时) | Spring AOP(无接口时)、Hibernate 延迟加载、EasyMock/jMock |
Spring 如何使用 CGLIB:两个典型场景
1. Spring AOP 方法拦截
Spring AOP 优先用 JDK 动态代理;只有当目标类没有实现任何接口时,才回退(或显式配置 proxyTargetClass=true)使用 CGLIB 生成子类代理,在方法调用前后插入切面逻辑(比如 @Transactional 开启/提交事务)。
具体怎么插入逻辑: CGLIB 生成的子类会重写目标方法,方法体不再是原始逻辑,而是委托给一个 MethodInterceptor.intercept(...);拦截器方法拿到一个 MethodProxy(能调用父类的原始实现),自己决定什么时候调用原始逻辑——可以在调用前执行代码、调用后再执行代码,甚至完全不调用原始逻辑直接返回别的结果。这套「方法调用前/后插入逻辑」的能力,正好对应 AOP 的几种通知(advice):
| Advice 类型 | 拦截器行为 |
|---|---|
前置通知(@Before) |
先执行自己的代码,再调用 methodProxy.invokeSuper(...) |
后置/返回通知(@After/@AfterReturning) |
先调用原方法,再执行自己的代码 |
环绕通知(@Around) |
完全控制原方法调不调用、什么时候调用——@Transactional 就是典型的环绕逻辑:开事务 → 调用原方法 → 提交或回滚事务 |
需要强调:CGLIB 本身不知道「AOP」这个概念,它只提供通用的方法拦截能力(MethodInterceptor);是 Spring AOP 在 CGLIB 生成的代理之上,把「拦截到的调用」和「切面逻辑」关联起来,才组成完整的 AOP 功能。CGLIB(连同 JDK 动态代理)只是这套机制里「生成代理 + 拦截调用」的底层实现选项。
2. @Configuration 类的 Full 模式代理
这是容易被面试问到、也最能体现"为什么需要 CGLIB"的例子。@Configuration 类默认 proxyBeanMethods = true,Spring 会用 CGLIB 生成这个配置类的子类代理:
@Configuration
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA(serviceB()); // 经 CGLIB 代理拦截,拿到容器里的单例 ServiceB
}
@Bean
public ServiceB serviceB() {
return new ServiceB();
}
}
如果没有 CGLIB 代理,serviceA() 内部调用 serviceB() 就是一次普通 Java 方法调用,每次都会 new 出新对象,破坏单例语义。CGLIB 生成的子类拦截了这次方法调用,转而去容器里取已注册的单例——这就是 Spring 官方文档里说的 Full 模式。相对地,把 @Bean 方法写在普通 @Component 类里则是 Lite 模式,没有这层代理,方法互调不保证走容器。
Spring 内嵌的 CGLIB:org.springframework.cglib
Spring Framework 没有直接依赖外部发布的 cglib jar,而是把 CGLIB 源码 fork 后重新打包进了 spring-core 模块,包名改成了 org.springframework.cglib(源码目录见 spring-core/src/main/java/org/springframework/cglib)。这样做首先是为了避免类路径版本冲突:应用自己引入的任何版本的 cglib,都不会影响 Spring 内部使用的这一份。
更关键的是维护责任的转移。Spring 团队对这份 fork 有持续的实际维护(该目录近期仍有提交,例如针对 ClassNameReader 的 ASM API 优化),会跟进修复兼容性问题、适配新版 JDK 字节码格式。所以即使原始 CGLIB 项目已经停止维护(见下文「背景:原始 CGLIB 项目」),Spring 的 AOP 代理、@Configuration Full 模式代理依旧能在 JDK 17、21 等新版本上正常工作——靠的是 Spring 自己维护的这份 fork,而不是上游发布的 cglib jar。
背景:原始 CGLIB 项目(历史信息)
Spring 内嵌的这份 CGLIB fork 源自一个独立的开源项目:代码托管在 github.com/cglib/cglib,采用 Apache-2.0 协议,Maven 坐标是 groupId: cglib、artifactId: cglib(或不依赖外部 ASM 的 cglib-nodep)。它不属于某个公司或基金会,而是由多名贡献者零散维护。
这个上游项目目前基本处于无人维护状态:官方仓库 README 明确写明在 JDK 17+ 上可能无法正常工作甚至完全不可用,并建议需要支持新版 JDK 的项目改用 ByteBuddy。这也是 Spring 当初把它 fork 进自己代码库、自行维护的原因之一——避免长期依赖一个已经停止更新的第三方库。
延伸阅读
- Hibernate 用 CGLIB 为持久化对象(Persistent Object)生成字节码代理,实现懒加载(对集合的延迟抓取则依赖其他机制)。
- EasyMock、jMock 用 CGLIB 为没有接口的类生成 mock 对象,支撑单元测试里的桩替换。
- 除 CGLIB 外,Groovy、BeanShell 等脚本语言也基于 ASM 生成 Java 字节码;不建议直接手写 ASM,因为它要求对 JVM 内部结构(class 文件格式、字节码指令集)有较深了解,详见 Java ASM 与运行时字节码织入。
维护记录
| 时间 | 修改内容 | 原因 |
|---|---|---|
| 2026-06-30 | 补充 JDK 动态代理 vs CGLIB 对比、Spring AOP 与 @Configuration Full 模式代理的具体机制与示例;标题从 cglib 改为 CGLIB 动态代理;标签由 reprint 改为 remix + AI-assisted |
原文内容过于简单(只有背景介绍),补充实际使用场景和面试常问的原理细节 |
| 2026-06-30 | 补充项目维护现状(JDK 17+ 不建议使用、官方建议迁移 ByteBuddy)、Spring 内嵌 fork 的处理方式、项目原始仓库地址 | 说明 CGLIB 的归属、维护方与当前可用性,便于读者判断是否该在新项目中继续使用 |
| 2026-06-30 | 修正 Spring 内嵌 CGLIB 一节:说明上游 cglib/cglib 的"无人维护、JDK 17+ 可能不可用"结论不能直接套用到 Spring,Spring 自行维护 org.springframework.cglib fork 并持续修复兼容性 |
原表述容易让读者误以为 Spring 里的 CGLIB 也不支持新版 JDK,与实际情况不一致 |
| 2026-06-30 | 调整全文结构与标题(CGLIB 动态代理 → Spring CGLIB 动态代理),以 Spring 内嵌的 org.springframework.cglib 为主线,原始 CGLIB 项目信息与 JDK 17+ 维护现状收入独立的"背景:原始 CGLIB 项目"章节作为历史/关联信息 |
明确文章定位为 Spring CGLIB 主文档,区分主线内容与背景资料 |
| 2026-06-30 | 将正文中的 “ASM” 关联到站内文章 Java ASM 与运行时字节码织入 | 站内已有专门讲 ASM 的文章,补充内链方便读者深入了解底层机制 |
| 2026-06-30 | 补充 CGLIB 与 ASM 的分工说明(CGLIB 决定生成什么样的子类,ASM 负责写成字节码);展开 AOP 方法拦截一节,说明 MethodInterceptor/MethodProxy 机制与前置/后置/环绕通知的对应关系 |
读者 Q&A 沉淀,澄清 CGLIB 和 ASM 的层次关系、CGLIB 如何支撑 AOP 语义 |
原始来源:http://baike.baidu.com/view/1254036.htm?fr=aladdin 项目仓库:https://github.com/cglib/cglib