← 返回题目列表

JDK 动态代理和 CGLIB 动态代理有什么区别?

高频 中等 第 12 / 25 题 更新于 2026/07/28
代理模式JDK动态代理CGLIBSpring AOP

简化版

JDK 动态代理基于接口,运行期生成实现接口的代理类;CGLIB 基于继承,运行期生成目标类的子类。目标类有接口时可用 JDK 动态代理,没有接口时通常用 CGLIB,但 CGLIB 不能代理 final 类和 final 方法。

详细版

二者核心区别如下:

对比项JDK 动态代理CGLIB 动态代理
实现方式生成接口实现类生成目标类子类
是否需要接口需要不需要
核心回调InvocationHandlerMethodInterceptor
调用目标method.invoke(target, args)methodProxy.invokeSuper(obj, args)
对 final 的限制接口代理,不直接受类 final 影响不能代理 final 类/方法
Spring AOP 场景目标有接口时常用目标无接口或强制类代理时常用

JDK 动态代理示意:

代理类 implements 接口 -> InvocationHandler -> 目标对象

CGLIB 示意:

代理类 extends 目标类 -> MethodInterceptor -> super 原方法

面试回答要注意:不要简单说谁性能一定更好。早期 CGLIB 常被认为调用性能较好,但现代 JDK 和框架不断优化,实际选择通常先看“有没有接口、是否允许继承、框架配置”,性能不是首要判断标准。

完整版教学

一、从生成代理类的角度理解区别

JDK 动态代理生成的代理类实现接口。

如果有接口:

interface UserService {
    void createUser();
}

JDK 大致生成:

class $Proxy0 implements UserService {
    private InvocationHandler handler;

    public void createUser() {
        handler.invoke(this, method, null);
    }
}

CGLIB 生成的是目标类子类:

class UserServiceProxy extends UserService {
    public void createUser() {
        interceptor.intercept(...);
    }
}

一个走接口,一个走继承,这是所有差异的源头。

二、从调用链理解区别

JDK 动态代理:

proxy.method()
  -> InvocationHandler.invoke()
  -> method.invoke(target, args)
  -> target.method()

CGLIB 动态代理:

proxy.method()
  -> MethodInterceptor.intercept()
  -> methodProxy.invokeSuper(proxy, args)
  -> super.method()

因此 JDK 动态代理更像“接口转发器”,CGLIB 更像“子类增强器”。

三、从限制条件理解区别

JDK 动态代理最大限制是必须有接口。如果业务类没有接口,就无法直接创建接口代理。

CGLIB 最大限制是继承规则。目标类不能是 final,被增强的方法不能是 finalprivate

例如:

final class PaymentService {
    public void pay() {}
}

CGLIB 无法继承这个类。

class PaymentService {
    public final void pay() {}
}

CGLIB 可以生成子类,但不能重写 pay(),因此无法对这个方法做增强。

四、Spring AOP 中如何选择

Spring AOP 常见规则可以这样理解:

  • 有接口时,可以使用 JDK 动态代理;
  • 没有接口时,使用 CGLIB;
  • 如果配置强制使用类代理,也会走 CGLIB;
  • 最终注入到容器中的通常是代理对象,不是原始目标对象。

这会影响类型注入。例如 JDK 动态代理生成的是接口代理,如果你按具体实现类注入,可能会出现类型不匹配;CGLIB 代理是子类,按实现类注入通常更自然。

这个点在 Spring 面试中很常见。

五、性能问题怎么答更稳

不要背“JDK 慢,CGLIB 快”这种绝对结论。

原因有三点:

第一,JDK 反射和动态代理一直在优化。

第二,CGLIB 创建代理类、方法拦截也有自己的成本。

第三,业务系统里代理开销通常远小于数据库、网络、缓存、序列化等开销。

更稳的回答是:选型主要看接口约束、继承限制和框架配置;性能需要结合具体版本和场景测试,不能脱离上下文绝对判断。

六、代理选择不能只看性能口号

若目标 Bean 实现接口并按接口注入,JDK 代理即可;若必须按具体类类型注入,则要评估 CGLIB 及 final 限制,而不是凭“快几纳秒”决定。

has interface? --yes--> JDK proxy; --no/force class--> CGLIB subclass

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

七、边界、代价与验证

检查维度应确认的内容
正确性选型先看类型契约和限制,再用基准测试谈性能,不能背“某一种永远更快”。
适用边界现代 JVM 下二者性能差距通常不是首要矛盾,接口设计、类型兼容和可代理性更重要。
测试证据覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序
运行成本记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量

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

易错点:选型先看类型契约和限制,再用基准测试谈性能,不能背“某一种永远更快”。

八、常见误区与追问

  • 误区:CGLIB 在所有版本中都一定比 JDK 代理快。 生成和调用机制、JDK 版本及业务负载都会影响结果,没有脱离基准条件的固定结论。
  • 误区:代理类存在,就代表所有调用都会经过代理。 只有客户端持有并调用代理引用时增强才生效;绕过代理直接调用目标对象,调用链自然不会出现增强。
  • 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
  • 追问:Spring 中有接口就绝对使用 JDK 代理吗? 默认通常如此,但配置可强制类代理;实际还要结合 Spring 版本和代理配置判断。
  • 追问:代理模式和装饰器模式能只靠类图区分吗? 不能;两者都可能包装同一接口,必须结合“控制访问”还是“叠加职责”的设计意图区分。
  • 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。

九、加强记忆

JDK 动态代理靠接口,CGLIB 动态代理靠继承。接口充足时 JDK 简洁,没接口时 CGLIB 补位;但 CGLIB 怕 final,性能不是第一判断标准。