一、cglib 是什么
cglib(Code Generation Library)是一个强大的字节码生成库,它基于 ASM 实现,能够在运行时动态生成 Java 类的子类。与 JDK 自带的动态代理(java.lang.reflect.Proxy)不同,cglib 不要求目标类必须实现接口——它通过生成目标类的子类来实现代理,这使得它可以代理任何非 final 的类。
cglib 在 Java 后端生态中占据核心地位。Spring AOP 在目标类没有实现接口时,默认使用 cglib 来创建代理对象;Hibernate 使用 cglib 来实现延迟加载(lazy loading)的代理;Mockito 在早期版本也使用 cglib 来创建 mock 对象。
cglib 的核心类包括:
net.sf.cglib.proxy.Enhancer:代理对象的创建器net.sf.cglib.proxy.MethodInterceptor:方法拦截器接口net.sf.cglib.proxy.Callback:所有拦截器的基接口net.sf.cglib.proxy.FixedValue:返回值固定的拦截器net.sf.cglib.proxy.NoOp:不做任何拦截,直接调用父类方法net.sf.cglib.proxy.LazyLoader:延迟加载拦截器net.sf.cglib.reflect.FastClass:FastClass 机制,绕过反射提高调用性能
二、JDK 动态代理 vs cglib 动态代理
这是面试中的经典对比问题。两者的本质区别如下:
2.1 JDK 动态代理
JDK 动态代理基于接口(interface-based),通过 java.lang.reflect.Proxy 和 InvocationHandler 实现。
// 目标接口 |
JDK 动态代理的核心限制:只能代理实现了接口的类。
在字节码层面,JDK 动态代理生成的代理类($Proxy0)直接继承 java.lang.reflect.Proxy,而 Java 是单继承的,因此代理类无法再继承其他具体的类——只能实现目标接口。
2.2 cglib 动态代理
cglib 通过生成目标类的子类来实现代理(subclass-based):
import net.sf.cglib.proxy.Enhancer; |
2.3 特性对比表
| 维度 | JDK 动态代理 | cglib 动态代理 |
|---|---|---|
| 代理方式 | 基于接口 | 基于子类继承 |
| 目标类要求 | 必须实现至少一个接口 | 不能是 final 类 |
| 方法要求 | 接口中定义的方法 | 不能是 final/static 方法 |
| 生成的是 | Proxy 的子类 + 接口实现 |
目标类的子类 |
| 内部调用 | 不会触发代理(调的是原始对象) | 不会触发代理(调的是父类方法) |
| 性能(创建) | 快(只生成一个类) | 慢(生成子类 + FastClass,字节码量大) |
| 性能(调用) | 慢(反射 invoke) | 快(FastClass 索引访问) |
| 依赖 | JDK 内置 | 需引入 cglib(或 spring-core 自带的 repackaged 版本) |
三、Enhancer 的工作原理
3.1 Enhancer 的核心流程
Enhancer 是 cglib 创建代理对象的入口。其工作流程分为以下步骤:
1. setSuperclass(targetClass) → 设置目标类为生成的代理类的父类 |
3.2 生成的代理类字节码结构
假设目标类如下:
public class Calculator { |
cglib 生成的代理类结构(反编译后的近似代码):
public class Calculator$$EnhancerByCGLIB$$12345 extends Calculator |
关键观察:
- 代理类继承目标类,因此所有非 final 的 public/protected 方法都会被重写
- 每个方法生成一个 CGLIB$ 前缀的镜像方法,该镜像方法直接调用父类(即目标类)的对应方法(使用
super.xxx()) - 重写方法被声明为 final,以防止代理类的子类再次重写导致无限循环
MethodProxy是关键,它包装了 FastClass 机制,使得proxy.invokeSuper()能够快速调用到镜像方法
3.3 MethodProxy 与 FastClass 机制
cglib 最精妙的设计之一就是 FastClass 机制。传统 JDK 动态代理中,每次方法调用都需要 method.invoke(target, args)——这是 Java 反射调用,性能较差。cglib 通过 FastClass 机制绕过了反射。
FastClass 的核心思想:为每个类生成一个索引表,将方法名映射为整数索引,然后通过 switch-case 进行方法分发。
FastClass 的生成逻辑(简化版):
// 为 Calculator 类生成的 FastClass |
MethodProxy 的 invokeSuper 方法的底层实现:
// MethodProxy.java (简化逻辑) |
调用链路总结:
proxy.add(1, 2) |
四、cglib 的局限性
4.1 final 限制
cglib 的第一个重大限制:无法代理 final 类。因为 Java 不允许继承 final 类。
public final class ImmutableService { ... } |
同样,final 方法也无法被代理。cglib 通过子类重写方法来实现拦截,而 final 方法不能被重写:
public class BaseService { |
Spring AOP 的应对策略:Spring AOP 默认使用 JDK 动态代理(接口模式),当没有接口时才回退到 cglib。从 Spring Boot 2.0 开始,spring.aop.proxy-target-class 默认为 true,即默认使用 cglib。如果目标方法是 final 的,Spring 会打印警告日志并跳过该方法的代理。
4.2 构造函数限制
cglib 生成的代理类会调用父类的构造方法。如果目标类没有无参构造方法,你需要显式传递参数:
Enhancer enhancer = new Enhancer(); |
特别提醒:cglib 创建代理对象时会调用两次构造方法——一次是 Enhancer.create() 直接调用代理类的构造方法,另一次是代理类的构造方法内部调用父类(目标类)的构造方法。因此,不要在构造方法中执行有副作用的逻辑(如开启数据库连接、启动线程等),否则这些逻辑会被执行多次。
4.3 内部调用问题
与 JDK 动态代理一样,cglib 也无法拦截目标类内部的方法调用(self-invocation):
public class OrderService { |
解决方案:使用 AopContext.currentProxy() 获取当前代理对象(Spring 中需要 @EnableAspectJAutoProxy(exposeProxy = true)),或者将内部调用的方法提取到另一个 Bean 中。
4.4 包名和类名冲突
cglib 生成的代理类名为 目标类名$$EnhancerByCGLIB$$随机数。这些类存在于 JVM 的 Metaspace 中。在大量创建代理类的场景下(如 Spring 应用中每个原型 Bean 都会生成一个代理类),需要注意:
- 代理类的数量可能导致 Metaspace 溢出(
OutOfMemoryError: Metaspace) - 频繁创建代理类会产生大量的类加载开销
4.5 equals/hashCode/toString 的默认行为
cglib 默认不会拦截 equals()、hashCode()、toString() 方法。这三个方法的默认行为继承自 java.lang.Object。如果需要拦截它们,需要设置不同的 CallbackFilter。
五、cglib 的其他拦截器类型
5.1 CallbackFilter —— 按方法名选择不同的拦截策略
Enhancer enhancer = new Enhancer(); |
5.2 LazyLoader —— 延迟加载
Enhancer enhancer = new Enhancer(); |
Hibernate 的延迟加载就是基于同样的原理:代理对象持有对真实对象的引用,只有当真正访问数据时才触发 SQL 查询。
5.3 Dispatcher —— 每次调用都重新分发
与 LazyLoader 类似,但 Dispatcher 的 loadObject() 在每次方法调用时都会被调用:
enhancer.setCallback(new Dispatcher() { |
5.4 BeanCopier —— 对象属性拷贝
cglib 还提供了基于字节码生成的高性能 Bean 属性拷贝工具:
BeanCopier copier = BeanCopier.create(Source.class, Target.class, false); |
六、Spring AOP 中的 cglib
6.1 Spring 如何选择代理方式
Spring AOP 的核心代理创建逻辑在 org.springframework.aop.framework.DefaultAopProxyFactory:
public class DefaultAopProxyFactory implements AopProxyFactory { |
Spring 的判断逻辑:
- 如果目标类实现了接口且
proxyTargetClass为 false → JDK 动态代理 - 如果目标类没有实现接口 → cglib(ObjenesisCglibAopProxy)
- 如果
proxyTargetClass为 true → cglib
6.2 Spring 对 cglib 的优化
Spring 不是直接使用原始 cglib,而是对其进行了封装和优化:
- ObjenesisCglibAopProxy:Spring 使用 Objenesis 库来绕过构造方法调用,避免 cglib 创建代理时调用两次构造方法的问题。
- Spring 4.0+ 内嵌 cglib:从 Spring 3.2 开始,cglib 的代码被重新打包到
spring-core的org.springframework.cglib包下,不再需要外部依赖。 - @Configuration 代理:Spring 使用 cglib 代理
@Configuration类,确保@Bean方法的单例语义(防止同一个 @Bean 方法被多次调用时创建多个实例)。
七、Android 平台的动态代理替代方案
Android 上不能直接使用 cglib(因为 cglib 依赖 JVM 的类加载机制和 sun.misc.Unsafe,这在 ART 上不可用)。Android 平台有几种替代方案:
7.1 JDK 动态代理
Android 支持 java.lang.reflect.Proxy,但同样只能代理接口。这是 Android 上最常用且可靠的方式:
Retrofit retrofit = new Retrofit.Builder() |
Retrofit 通过 JDK 动态代理将接口方法调用转换为 HTTP 请求,这是 Android 上最经典的动态代理应用。
7.2 DexMaker
DexMaker 是 LinkedIn 开发的一个用于在 Android 运行时生成 DEX 代码的库,类似于 cglib 在 JVM 上的作用。它可以动态生成 DEX 字节码并加载到当前 ClassLoader 中。
// DexMaker 使用示例(类似 cglib 的 Enhancer) |
Mockito 在 Android 上的早期版本就使用了 DexMaker 来生成 mock 对象的 DEX 代码。
7.3 编译期代码生成(推荐方式)
相比运行时动态代理,Android 生态更推荐在编译期通过 APT 或 Gradle Transform 来生成代码。这种方式不依赖运行时的类加载机制,且性能更好:
- Dagger2:使用 APT 在编译期生成依赖注入代码
- Room:使用 APT 生成数据库访问代码
- DataBinding:使用 APT 生成绑定代码
- 自定义 Gradle Plugin + ASM:在 Transform 阶段修改字节码
八、cglib 相关源码路径
| 组件 | 源码路径 |
|---|---|
| cglib Enhancer | cglib/cglib/src/main/java/net/sf/cglib/proxy/Enhancer.java |
| cglib FastClass | cglib/cglib/src/main/java/net/sf/cglib/reflect/FastClass.java |
| cglib MethodProxy | cglib/cglib/src/main/java/net/sf/cglib/proxy/MethodProxy.java |
| cglib KeyFactory | cglib/cglib/src/main/java/net/sf/cglib/core/KeyFactory.java |
| Spring AOP cglib | spring-framework/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java |
| Spring ObjenesisCglibAopProxy | spring-framework/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java |
| JDK Proxy | jdk/src/java.base/share/classes/java/lang/reflect/Proxy.java |
| ASM (cglib 的底层) | asm/asm/src/main/java/org/objectweb/asm/ClassWriter.java |
九、常见面试题
Q1: cglib 如何实现方法拦截?生成的代理类与目标类是什么关系?
A: cglib 通过生成目标类的子类来实现方法拦截。生成的代理类继承目标类,重写所有非 final、非 static 的方法。在每个重写方法中,cglib 不直接调用父类方法,而是先检查是否设置了 MethodInterceptor 回调,如果设置了,就调用 interceptor.intercept() 方法,将控制权交给开发者;如果没有设置,才直接调用父类方法。代理类还会为每个被重写的方法生成一个 CGLIB$ 前缀的”镜像方法”,该方法内部直接调用 super.xxx()——这保证了在 MethodInterceptor 中可以通过 proxy.invokeSuper() 获取到原始方法的调用。这种设计实现了”拦截器模式”,将方法调用的控制权完全交给开发者。
Q2: MethodProxy.invokeSuper() 和 Method.invoke() 在 cglib 中的区别是什么?为什么前者更快?
A: 两者的核心区别在于调用方式。Method.invoke(obj, args) 是标准的 Java 反射调用,每次调用都需要经过访问权限检查、参数包装(自动装箱拆箱)、以及 JVM 内部的 native 方法调用(NativeMethodAccessorImpl.invoke0),这些开销很大。而 MethodProxy.invokeSuper(obj, args) 使用的是 cglib 的 FastClass 机制:FastClass 为每个类生成了一个 int-to-method 的映射表,通过 switch-case 直接调用目标方法——这本质上是普通的 Java 方法调用,没有任何反射开销。FastClass 的索引通过方法签名(方法名 + 参数类型列表)的哈希来计算,第一次调用时会缓存索引,后续调用直接使用缓存。根据 cglib 的文档,FastClass 比反射调用快约 10-20 倍。
Q3: 为什么 cglib 不能代理 final 方法和 final 类?在 Spring 中如何应对这种情况?
A: cglib 通过生成子类来工作,这是根本原因。Java 规范不允许任何类继承 final 类,也不允许子类重写 final 方法——这是 JVM 在类加载验证阶段就强制实施的约束(定义在 JVM 规范 4.10 节)。Spring AOP 面对这种情况时有两种策略:(1) 如果目标类实现了接口,Spring 会优先使用 JDK 动态代理(即使 proxyTargetClass=true 也无法绕过 final 限制);(2) 如果目标方法被标记为 final,Spring 会在日志中输出警告(如 “Unable to proxy method [finalMethod] because it is final”),并且该 final 方法不会被切面拦截,而其他非 final 方法正常被代理。从设计角度来看,如果你打算使用 AOP,应该避免将业务方法声明为 final。
Q4: cglib 创建代理时调用了几次构造方法?可能导致什么问题?如何解决?
A: cglib 创建代理对象时,实际上调用了 2 次构造方法:(1) 代理类的构造方法(由 Enhancer.create() 通过反射调用);(2) 代理类构造方法内部通过 super() 调用的目标类构造方法。如果目标类的构造方法中包含有副作用的代码(如连接数据库、注册监听器、启动线程、修改静态变量等),这些副作用会被执行两次——一次是实际的目标类实例创建时,另一次是 cglib 创建代理类的过程中。Spring 通过使用 Objenesis 库解决了这个问题:Objenesis 可以不经过构造方法(bypass constructor)直接实例化对象,从而避免了第二次构造方法调用。这就是为什么 Spring 的 cglib 代理使用的是 ObjenesisCglibAopProxy 而非原始的 CglibAopProxy。
Q5: 在什么情况下 JDK 动态代理比 cglib 更好?什么情况下反过来?
A: 选择策略如下。(1) 使用 JDK 动态代理的场景:目标类有清晰的接口定义;接口方法数量较稳定(不会频繁增删);希望减少依赖(JDK 内置);不希望引入额外的字节码生成开销(Metaspace 占用)。(2) 使用 cglib 的场景:目标类没有实现接口(如一些遗留代码);需要代理类中的具体方法(如 protected 方法);需要更高的调用性能(FastClass 机制)。(3) 在 Spring 应用中,遵循默认行为即可:默认先尝试 JDK 动态代理,无法满足时回退到 cglib。从 Spring Boot 2.0 开始,proxy-target-class 默认为 true,意味着默认使用 cglib。(4) 在性能敏感的场景中,如果代理创建不是瓶颈但方法调用频率极高,cglib 的 FastClass 有明显优势;如果代理创建频繁(如大量原型 Bean),JDK 动态代理的创建成本更低。
Q6: Android 上为什么不能使用 cglib?有哪些替代方案?
A: Android 不能使用 cglib 的原因是多方面的。(1) 字节码格式不同:cglib 生成的是 JVM .class 文件,而 Android 使用的是 DEX 格式。(2) 类加载机制不同:cglib 依赖 ClassLoader.defineClass() 来动态加载生成的类,而 ART/Dalvik 使用 DexClassLoader,不兼容 JVM 的类定义方式。(3) cglib 内部使用了 sun.misc.Unsafe 和一些 JVM 专有的内部 API,这些在 ART 上不存在或被严格限制。(4) 安全限制:Android 不允许应用动态生成可执行的 DEX 代码并加载(除非使用 DexMaker,且有相应的权限和限制)。Android 上的替代方案包括:(a) JDK 动态代理(java.lang.reflect.Proxy),用于接口代理,如 Retrofit;(b) 编译期代码生成(APT、KSP),如 Dagger2、Room;(c) DexMaker,用于测试中的 mock 对象;(d) Gradle Transform + ASM,在编译阶段修改字节码。
参考文档:
- cglib GitHub: https://github.com/cglib/cglib
- JVM Specification §4.10: Verification of class Files
- Spring AOP Documentation: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop
- ASM Guide: https://asm.ow2.io/asm4-guide.pdf
- DexMaker: https://github.com/linkedin/dexmaker




