★ JDK 动态代理与 CGLIB 区别与选择

结论先行:两者都用于在运行期生成代理对象以拦截方法调用。JDK 动态代理基于接口:目标必须实现接口,代理类实现同一接口,通过 InvocationHandler.invoke 分发;CGLIB 基于继承:用字节码生成目标类的子类,通过 MethodInterceptor 拦截,不要求接口,但不能代理 final 类、不能覆写 final/static 方法。Spring 的选择逻辑:目标类有接口时默认可用 JDK 代理,无接口必须 CGLIB;Spring Boot 2.x 起默认 proxy-target-class = true,即一律 CGLIB(除非显式关闭)。

一、原理对比表

维度JDK 动态代理CGLIB
机制生成实现目标接口的代理类字节码生成目标类的子类
前提目标必须有接口目标类可继承(非 final)、方法非 final
拦截器InvocationHandler.invokeMethodInterceptor.intercept
可代理范围仅接口声明的方法类中可覆写的 public/protected 方法
额外依赖JDK 自带cglib/objenesis(Spring 已内嵌 repackaged 版本)
特点类型要强转成接口无需接口;Boot 2+ 默认使用

二、Spring 如何选择

  • DefaultAopProxyFactory:proxyTargetClass = true → CGLIB;否则目标有接口 → JDK 代理,无接口 → CGLIB;
  • Spring Boot 2.0+ 默认 true(spring.aop.proxy-target-class=true),因此现代项目绝大多数 Bean 都是 CGLIB 代理;
  • Spring Framework 6 起仍在用 CGLIB(内部重打包),并没有移除;JDK17+ 对原生动态代理做了优化,两者性能差异已很小。
// JDK 动态代理(手写示意)
PayService proxy = (PayService) Proxy.newProxyInstance(
        PayService.class.getClassLoader(),
        new Class[]{PayService.class},
        (p, method, args) -> { System.out.println("before"); return method.invoke(target, args); });

// CGLIB(手写示意)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);                 // 子类化,无需接口
enhancer.setCallback((MethodInterceptor) (obj, m, args, mp)
        -> { System.out.println("before"); return mp.invoke(target, args); });
OrderService proxy = (OrderService) enhancer.create();

常见追问 / 记忆点

  • 追问 1:哪些情况 CGLIB 也救不了?答:final 类、final 方法、static 方法、private 方法——这也是 @Transactional 写在 final/private 方法上失效的原因之一。
  • 追问 2:为什么 Boot 默认 CGLIB?答:很多类没接口(如直接 @Service 无接口实现),统一 CGLIB 免去"有接口没接口行为不一致"的坑。
  • 追问 3:性能谁更好?答:JDK 代理每调用走反射 invoke(JDK17+ 已优化);CGLIB 子类调用接近原生。工程上按默认即可,不必过度纠结。
  • 记忆点:"JDK 认接口,CGLIB 认子类;final/static/private 两边都不管;Boot 默认 CGLIB"。
笔记加载中…