← 返回题目列表

CGLIB 动态代理的原理是什么?

高频 中等 第 10 / 25 题 更新于 2026/07/28
代理模式CGLIB动态代理字节码增强

简化版

CGLIB 动态代理通过运行期生成目标类的子类,并在子类中重写方法来加入增强逻辑。它不要求目标类实现接口,但不能代理 final 类和 final 方法,因为这些无法被继承或重写。

详细版

CGLIB 的核心思路是“继承 + 方法拦截”。它会生成一个目标类的子类,调用方法时先进入拦截器,再决定是否调用父类原方法。

典型伪代码:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    System.out.println("调用前增强");
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("调用后增强");
    return result;
});

UserService proxy = (UserService) enhancer.create();
proxy.createUser("Tom");

关键点:

  • CGLIB 代理的是类,不要求接口;
  • 代理类是目标类的子类;
  • 方法调用会进入 MethodInterceptor
  • 通常通过 methodProxy.invokeSuper() 调用父类原方法;
  • final 类无法继承,final 方法无法重写,因此不能被 CGLIB 正常代理增强。

在 Spring AOP 中,如果目标类没有接口,常见做法就是使用 CGLIB 代理。现代 Spring 也可以配置强制使用 CGLIB。

完整版教学

一、为什么需要 CGLIB

JDK 动态代理要求目标对象实现接口。但现实项目里并不是所有类都有接口。

比如:

class UserService {
    public void createUser(String name) {
        System.out.println("创建用户");
    }
}

这个类没有实现接口,JDK 动态代理就不适合直接代理它。

CGLIB 的思路是:既然没有接口,那就生成一个子类。

可以理解为运行期生成了类似这样的类:

class UserServiceProxy extends UserService {
    public void createUser(String name) {
        before();
        super.createUser(name);
        after();
    }
}

真实实现更复杂,但这个模型足够用于面试说明原理。

二、CGLIB 的调用流程

CGLIB 代理调用大致是:

客户端调用代理子类方法

代理子类拦截方法调用

进入 MethodInterceptor

执行前置增强

invokeSuper() 调用父类原方法

执行后置增强并返回

与 JDK 动态代理相比:

  • JDK 动态代理通过接口转发到 InvocationHandler
  • CGLIB 通过子类重写方法转发到 MethodInterceptor

这就是二者最核心的实现差异。

三、为什么 final 会影响 CGLIB

CGLIB 依赖继承和方法重写。如果类是 final

final class UserService { }

它不能被继承,因此无法生成代理子类。

如果方法是 final

public final void createUser() { }

子类不能重写这个方法,因此无法在这个方法上织入增强逻辑。

如果方法是 private,子类也无法重写它,所以也不能被正常代理增强。

面试里这个点非常高频:CGLIB 不要求接口,但受继承规则限制。

四、CGLIB 和 Spring AOP 的关系

Spring AOP 可以使用 JDK 动态代理,也可以使用 CGLIB。常见判断是:

  • 目标类实现了接口:可以用 JDK 动态代理;
  • 目标类没有接口:使用 CGLIB;
  • 配置强制使用类代理:使用 CGLIB。

需要注意,Spring AOP 是基于代理的 AOP。它不是直接修改目标对象本身,而是把目标对象包装成代理对象。

这也解释了 Spring AOP 的一些经典限制,比如同类内部方法调用可能绕过代理。

五、CGLIB 的优缺点

优点:

  • 不需要接口;
  • 对普通类更友好;
  • 能统一增强类方法;
  • 在框架中使用广泛。

缺点:

  • 不能代理 final 类;
  • 不能增强 finalprivate 等不可重写方法;
  • 生成子类和字节码增强机制比 JDK 动态代理更复杂;
  • 构造方法、类加载等细节更容易影响代理行为。

所以 CGLIB 不是“比 JDK 动态代理更高级”,它只是解决了不同约束下的问题。

六、子类代理与方法拦截

目标类有 10 个可覆写方法和 2 个 final 方法时,CGLIB 只能拦截前 10 个;final 方法无法通过覆写建立拦截点。

caller -> generated subclass -> MethodInterceptor -> MethodProxy.invokeSuper()

这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。

七、边界、代价与验证

检查维度应确认的内容
正确性CGLIB 的关键不是“没有接口”,而是通过生成子类并覆写可拦截方法建立调用入口。
适用边界类和方法不能为 final,private 方法也不能被子类覆写;构造、可见性和模块边界同样要考虑。
测试证据覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序
运行成本记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量

模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。

易错点:CGLIB 的关键不是“没有接口”,而是通过生成子类并覆写可拦截方法建立调用入口。

八、常见误区与追问

  • 误区:CGLIB 可以代理任何类和任何方法。 final 类无法被继承,final/private 方法无法被覆写,因此存在明确限制。
  • 误区:代理类存在,就代表所有调用都会经过代理。 只有客户端持有并调用代理引用时增强才生效;绕过代理直接调用目标对象,调用链自然不会出现增强。
  • 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
  • 追问:invoke 和 invokeSuper 为什么不能随便混用? 错误地对代理对象再次 invoke 可能递归;invokeSuper 用于进入被代理的父类实现。
  • 追问:代理模式和装饰器模式能只靠类图区分吗? 不能;两者都可能包装同一接口,必须结合“控制访问”还是“叠加职责”的设计意图区分。
  • 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。

九、加强记忆

CGLIB 的记忆锚点是:没有接口也能代理,因为它生成子类;但也因为它依赖子类,所以怕 final。JDK 动态代理走接口,CGLIB 走继承,这是两者最关键的分界线。