bigywater.tech
Spring · 19

Spring AOP 怎么建代理:JDK 与 CGLIB 的选择

Spring AOP 什么时候用 JDK 动态代理、什么时候用 CGLIB?Boot 里的默认值为什么不同?两种代理对 instanceof 和按类注入有什么影响?创建和调用的成本各是多少?这篇在 Spring Framework 7.0.9 上逐项验证。

JDK / CGLIB
纯 Spring 默认下有接口的类与没有接口的类各自得到的代理
CGLIB
Spring Boot 不设任何 spring.aop.* 属性时有接口的类得到的代理
约 40 ns
一个什么都不做的拦截器,两种代理每次调用比直接调用(约 1.4 ns)多出的耗时

纯 Spring 的默认

实测 · 本机执行输出
  @EnableAspectJAutoProxy(默认):
    WithIface(有接口)的 Bean:JDK 动态代理,类名 jdk.proxy2.$Proxy17;instanceof Greeter=true;instanceof WithIface=false
    NoIface(没有接口)的 Bean:CGLIB 代理,类名 NoIface·SpringCGLIB·N;instanceof NoIface=true
    按具体类 WithIface 取 Bean:✗ NoSuchBeanDefinitionException

有接口走 JDK 代理,没有接口走 CGLIB;JDK 代理不是实现类的实例。

proxyTargetClass

实测 · 本机执行输出
  @EnableAspectJAutoProxy(proxyTargetClass = true):
    WithIface(有接口)的 Bean:CGLIB 代理,类名 WithIface·SpringCGLIB·N;instanceof Greeter=true;instanceof WithIface=true
    NoIface(没有接口)的 Bean:CGLIB 代理,类名 NoIface·SpringCGLIB·N;instanceof NoIface=true
    按具体类 WithIface 取 Bean:成功

设成 true 后所有代理都是 CGLIB,按具体类注入可用。

Boot 的默认

实测 · 本机执行输出
  Boot(@EnableAutoConfiguration,没有设任何 spring.aop.* 属性):
    WithIface(有接口)的 Bean:CGLIB 代理,类名 WithIface·SpringCGLIB·N;instanceof Greeter=true;instanceof WithIface=true
    NoIface(没有接口)的 Bean:CGLIB 代理,类名 NoIface·SpringCGLIB·N;instanceof NoIface=true
    按具体类 WithIface 取 Bean:成功
    spring.aop.proxy-target-class 的值 = (没设)

Boot 的 AOP 自动配置默认就用 CGLIB,不需要设属性。

外形

实测 · 本机执行输出
  JDK 代理的类实现的接口:[Greeter, SpringProxy, Advised, DecoratingProxy];能否强转成 WithIface:false
  CGLIB 代理的父类:WithIface;能调用 extra()(接口里没有的方法):extra

JDK 代理只实现接口,CGLIB 代理继承目标类。

成本

实测 · 本机执行输出
  为同一个目标类创建 2000 个代理:JDK 135 ms(新加载 1163 个类);CGLIB 69 ms(新加载 347 个类)
  每次调用 hi()(代理上挂一个什么都不做的 MethodInterceptor):JDK 代理 42 ns;CGLIB 代理 41 ns;直接调用 1.4 ns(sink=36000000)
  为同一个目标类创建 2000 个代理:JDK 127 ms(新加载 1163 个类);CGLIB 66 ms(新加载 347 个类)
  每次调用 hi()(代理上挂一个什么都不做的 MethodInterceptor):JDK 代理 47 ns;CGLIB 代理 41 ns;直接调用 1.4 ns(sink=36000000)
  为同一个目标类创建 2000 个代理:JDK 138 ms(新加载 1163 个类);CGLIB 76 ms(新加载 347 个类)
  每次调用 hi()(代理上挂一个什么都不做的 MethodInterceptor):JDK 代理 43 ns;CGLIB 代理 40 ns;直接调用 1.5 ns(sink=36000000)

调用开销两者相当,创建时 CGLIB 更快、加载的类更少。

分步演示

Spring AOP 的代理选择
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。第一、二、四、五节用不带 Boot 的 Spring Framework 7.0.9,第三节用 Spring Boot 4.0.8 的 @EnableAutoConfiguration(类路径上有 spring-boot-starter-aspectj);耗时是简单循环计时,没有用 JMH(调用成本的差别在几个 ns 以内,不能当精确值);JDK 代理创建时多加载的类数(1163 对 347)的原因我没有查
    • 没有验证:代理对象的 final 方法、private 方法的行为(见后面的 CGLIB 限制篇);objenesis 对构造器调用的影响;JDK 代理在 GraalVM native image 里的要求;spring.aop.proxy-target-class=false 在 Boot 里的效果;Kotlin 的 final 类与 allopen 插件
    京ICP备2026067502号-1京公网安备11010502063487号