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
对方法的限制只能代理接口中定义的方法无法代理 final、static、private 方法
创建代理的速度较快(直接利用 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”, 只是现代项目越来越少写接口。