Skip to content

一、 Spring AOP 的底层实现原理

Spring AOP 的核心原理是 动态代理 (Dynamic Proxy)

简单来说,当 Spring 容器启动并初始化 Bean 时,如果发现某个 Bean 被切面(Aspect)所“增强”(即配置了 AOP),Spring 不会直接返回原始对象,而是会创建一个代理对象 (Proxy Object) 返回给调用者。

执行流程如下:

  1. 容器启动: 解析 AOP 配置,找到所有需要被增强的 Bean。
  2. 创建代理: 在 Bean 初始化后(通常通过 BeanPostProcessor),Spring 会根据 Bean 的情况选择使用 JDK 动态代理CGLIB 动态代理 来生成一个代理对象。
  3. 调用拦截: 当你从容器中获取 Bean 并调用方法时,实际上调用的是代理对象的方法。
  4. 执行切面逻辑: 代理对象会在目标方法执行前后,执行配置好的拦截器链(Advice,如 @Before, @After 等),最后再通过反射或方法调用执行原始 Bean 的目标方法。

二、 两大代理模式详解

Spring AOP 在生成代理对象时,主要依靠以下两种技术:

1. JDK 动态代理 (JDK Dynamic Proxy)

这是 Java 语言原生支持的代理方式,位于 java.lang.reflect 包下。

  • 实现机制: 利用反射机制生成一个实现代理接口的匿名类。
  • 核心类: Proxy (创建代理对象) 和 InvocationHandler (处理方法调用)。
  • 要求: 目标对象必须实现至少一个接口。代理对象和目标对象是“兄弟”关系,它们都实现了同一个接口。

2. CGLIB 代理 (Code Generation Library)

这是一个强大的、高性能的代码生成包,Spring 内部集成了它。

  • 实现机制: 利用 ASM 开源包,在运行时动态生成目标类的子类(字节码级别操作)。
  • 核心原理: 通过继承的方式。代理对象是目标对象的“子类”,它会重写(Override)目标对象的所有非 final 方法,在子类中植入增强逻辑。
  • 要求: 目标对象不需要实现接口。但目标类不能是 final 的,目标方法也不能是 final 的(因为无法被继承或重写)。

三、 JDK 动态代理 vs CGLIB 的核心区别

维度JDK 动态代理CGLIB 动态代理
代理基础基于 接口 (Interface)基于 继承 (Inheritance)
底层技术Java 反射机制 (java.lang.reflect)ASM 字节码生成框架
对目标类的要求必须实现接口可以不实现接口,但类不能为 final
对方法的限制只能代理接口中定义的方法无法代理 finalstaticprivate 方法
创建代理的速度较快(直接利用 JDK 能力)较慢(需要生成复杂的字节码)
执行代理的速度早期较慢,但在 JDK 8+ 后性能大幅提升,与 CGLIB 差距缩小非常快(通过 FastClass 机制直接调用,非反射)
Spring 的选择如果目标对象实现了接口,默认用 JDK如果目标对象没实现接口,强制用 CGLIB

四、 深度补充:Spring Boot 的默认行为变化

这是一个加分项,展示你对 Spring 版本演进的了解:

  1. Spring Framework (传统 Spring):

    • 策略是:如果有接口,优先用 JDK 动态代理;如果没有接口,自动切换为 CGLIB。
    • 可以通过配置 proxy-target-class="true" 强制全部使用 CGLIB。
  2. Spring Boot 2.x 及以上:

    • 默认策略发生了变化。Spring Boot 默认将 spring.aop.proxy-target-class 设置为 true
    • 这意味着:默认情况下,Spring Boot 会优先使用 CGLIB 代理,即使目标类实现了接口。(除非你显式配置为 false)。
    • 原因: CGLIB 基于类代理,可以避免因类型转换(Interface vs Class)导致的 ClassCastException 问题,对开发者更友好。

五、 总结(一句话回答)

Spring AOP 底层利用动态代理技术。当目标类实现了接口时,默认使用 JDK 动态代理(基于反射和接口实现);当目标类没有实现接口时,使用 CGLIB(基于 ASM 字节码生成和子类继承)。在 Spring Boot 2.0 后,默认倾向于使用 CGLIB 以提供更好的稳定性和易用性。

结论先给你放前面,避免混淆 👇

❌ Spring Boot 3.5 默认不是 CGLIB✅ 默认仍然是:有接口 → JDK 动态代理;无接口 → CGLIB

这是 Spring Framework 6.x + Boot 3.x 的真实行为,在 JDK 21 下也没有变。

为什么很多人以为「Boot 3 默认是 CGLIB」?

这是一个非常典型的认知误区,原因主要有 4 个:

1️⃣ 现在大量项目「没有接口」

在 Boot 3 + DDD / 简化分层风格中:

java
@Service
public class OrderService {
    @Transactional
    public void create() {}
}

没有接口 ➡ Spring 只能用 CGLIB

于是你看到的几乎全是:

OrderService$$SpringCGLIB$$0

但这是「被迫选择」,不是「默认策略变了」。


2️⃣ 你用的是 @Transactional / @Async / @Cacheable

这些都走 Spring AOP 自动代理

text
AnnotationAwareAspectJAutoProxyCreator

而不是你手写的代理逻辑。

Spring 内部策略始终是:

java
if (hasInterface && !proxyTargetClass) {
    JDK proxy
} else {
    CGLIB
}

3️⃣ 你可能在某个地方“无意中”开启了 CGLIB

下面任何一个,都会 全局强制 CGLIB

配置文件

yaml
spring:
  aop:
    proxy-target-class: true

启动类

java
@EnableAspectJAutoProxy(proxyTargetClass = true)

某些 Starter(⚠️容易忽略)

  • Spring Security 某些配置
  • 自定义 AOP 配置类

4️⃣ 日志里看的是「目标类」,不是「接口」

JDK 动态代理:

text
com.sun.proxy.$Proxy123

很多同学没看到这个日志,就以为是 CGLIB。


二、Spring Boot 3.5 + JDK 21 的真实默认行为

场景代理方式
有接口JDK 动态代理(默认)
无接口CGLIB
设置 proxy-target-class=true强制 CGLIB

JDK 21 并没有改变 AOP 代理选择策略


三、Boot 3.5 + JDK 21 的 AOP「实战坑点」(重点🔥)

🚨 坑 1:@Transactional 在 final 方法上直接失效

java
@Transactional
public final void createOrder() {}

原因(CGLIB 场景):

  • CGLIB 通过 子类重写方法
  • final 方法 ❌ 不能被重写
  • 事务不会生效,但不会报错

✅ 建议:

  • 事务方法不要加 final
  • Service 类不要设计成 final

🚨 坑 2:同类方法调用(最常见)

java
@Service
public class OrderService {

    @Transactional
    public void create() {}

    public void test() {
        this.create(); // ❌ 事务失效
    }
}

JDK / CGLIB 都一样会失效

✅ 解决方式(推荐顺序):

  1. 拆 Bean(最推荐)
  2. 使用代理调用自己:
java
((OrderService) AopContext.currentProxy()).create();

⚠️ 需要:

java
@EnableAspectJAutoProxy(exposeProxy = true)

🚨 坑 3:接口 + 实现类注入类型不一致(JDK 代理专属)

java
public interface OrderService {}
@Service
public class OrderServiceImpl implements OrderService {}
java
@Autowired
private OrderServiceImpl orderService; // ❌

JDK 代理类型是:

text
$ProxyXX implements OrderService

❌ 无法注入实现类

✅ 正确姿势:

java
@Autowired
private OrderService orderService;

或:

yaml
spring.aop.proxy-target-class: true

🚨 坑 4:@Async / @Transactional 方法必须是 public

java
@Transactional
void save() {} // ❌

原因:

  • AOP 是通过 代理对象对外暴露的方法
  • protected / private 无法被代理调用

✔ 这在 Boot 3 + JDK 21 仍然成立


🚨 坑 5:构造方法、@PostConstruct 里调用事务方法

java
@PostConstruct
public void init() {
    createOrder(); // ❌
}

原因:

  • 此时 代理对象还没创建完成
  • 调用的是原始对象

四、怎么确认你现在用的是 JDK 还是 CGLIB?

方法 1:看 Bean 实际类型(推荐)

java
@PostConstruct
public void check() {
    System.out.println(this.getClass());
}

输出:

  • $Proxy123 → JDK
  • $$SpringCGLIB$$ → CGLIB

方法 2:Debug 断点

java
AbstractAutoProxyCreator#createProxy

五、一句话终极结论(直接可背)

Spring Boot 3.5 在 JDK 21 下,AOP 默认策略仍然是: 有接口用 JDK 动态代理,无接口用 CGLIB; 并非“默认全 CGLIB”, 只是现代项目越来越少写接口。