02-01-Spring-AOP
Spring AOP 知识点
本文聚焦 Spring AOP 的通用原理、源码链路与面试高频问题,按
Spring Boot 3.3.9 + Java 17 + Spring Framework 6.2.11的学习上下文整理。`。
说明:版本标注用于学习与调试上下文,实际项目的官方配套关系仍应以 BOM 与依赖树为准。
一、AOP 核心概念
1.1 什么是 AOP
AOP(Aspect-Oriented Programming,面向切面编程)用于把日志、事务、权限、缓存、限流这类横切关注点从业务代码中抽离出来,避免在每个 Service / Controller 中重复编写同类逻辑。
它和 OOP 的关注点不同:
- OOP 解决的是纵向职责拆分。
- AOP 解决的是横向通用逻辑复用。
1.2 核心术语
| 术语 | 含义 |
|---|---|
JoinPoint |
程序执行过程中可被切面拦截的位置 |
Pointcut |
用来筛选哪些 JoinPoint 会被拦截 |
Advice |
在连接点前后执行的增强逻辑 |
Aspect |
Pointcut + Advice 的组合 |
Weaving |
把切面织入目标对象的过程 |
Target |
原始业务对象 |
Proxy |
织入增强后的代理对象 |
1.3 五种常见 Advice
| 类型 | 注解 | 时机 | 典型用途 |
|---|---|---|---|
| Before | @Before |
方法执行前 | 参数检查、审计 |
| AfterReturning | @AfterReturning |
方法正常返回后 | 记录结果、补充日志 |
| AfterThrowing | @AfterThrowing |
方法抛异常后 | 异常监控、告警 |
| After | @After |
方法结束后 | 资源清理,类似 finally |
| Around | @Around |
方法前后全包裹 | 事务、缓存、限流、耗时统计 |
执行顺序通常可以理解为:
1 | @Around(before) |
@Around 能力最强,但也最容易写错,尤其是漏掉 proceed()、吞异常、错误修改返回值。
二、Spring AOP 与 AspectJ 的区别
| 维度 | Spring AOP | AspectJ |
|---|---|---|
| 织入时机 | 运行时 | 编译期 / 类加载期 / 运行时 |
| 实现方式 | 动态代理 | 字节码增强 |
| 连接点范围 | 仅方法执行 | 方法、字段、构造器、静态初始化等 |
| 依赖 | Spring 容器 | AspectJ 编译器 / Weaver |
| 复杂度 | 低 | 高 |
| 典型场景 | 事务、缓存、权限、日志 | 更底层、更广范围的横切需求 |
结论:
- 绝大多数业务系统场景,Spring AOP 足够。
- 只有在“方法级拦截不够用”时,才需要考虑 AspectJ。
三、Spring AOP 的核心执行链
本节重点不是记类名,而是理解“什么时候注册自动代理创建器、什么时候创建代理、调用时如何进入拦截器链”。
3.1 入口:开启自动代理
常见入口有两类:
- 显式配置
@EnableAspectJAutoProxy - 通过 Spring Boot 的 AOP 自动配置启用
@EnableAspectJAutoProxy 的关键点是:
1 | @Target(ElementType.TYPE) |
它本身不创建代理,而是借助 AspectJAutoProxyRegistrar 向容器注册自动代理创建器。
3.2 注册自动代理创建器
核心链路可以概括为:
1 | @EnableAspectJAutoProxy |
AnnotationAwareAspectJAutoProxyCreator 是整个 Spring AOP 的关键基础设施,它本质上是一个 BeanPostProcessor。
这意味着:
- 它会参与普通 Bean 的初始化过程。
- 它可以在 Bean 初始化完成后,判断当前 Bean 是否需要代理。
3.3 在 Bean 初始化后创建代理
Spring AOP 代理不是在扫描到 @Aspect 的那一刻就创建,而是在目标 Bean 初始化后创建。
典型链路:
1 | AbstractAutowireCapableBeanFactory.initializeBean() |
其中几个关键动作:
- 找出当前容器中所有可用的
Advisor - 判断哪些
Advisor能匹配当前 Bean - 如果存在匹配项,则使用
ProxyFactory创建代理 - 返回代理对象替换原始 Bean
3.4 代理类型选择
Spring AOP 会根据目标类型和配置选择代理方式:
- 目标类实现接口时,可用 JDK 动态代理
- 没有接口,或显式要求类代理时,使用 CGLIB
在 Spring Boot 的默认 AOP 配置下,通常会走类代理路径,即更常见地看到 $$SpringCGLIB$$。
3.5 方法调用时如何执行切面
代理创建完成后,真正的方法调用会进入拦截器链:
1 | 代理对象.method() |
可以把 proceed() 理解为“递归推进责任链”:
- 还有下一个拦截器,就继续调用下一个
- 没有下一个拦截器,就真正调用目标方法
四、JDK 动态代理与 CGLIB
| 维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 前提 | 目标类实现接口 | 不要求接口 |
| 原理 | 生成接口代理类 | 生成目标类子类 |
| 限制 | 只能代理接口方法 | 不能代理 final 类 / final 方法 |
| 常见标识 | $Proxy... |
$$SpringCGLIB$$... |
常见判断题:
private方法不能被 Spring AOP 代理final方法不能被 CGLIB 覆盖增强this.xxx()的内部调用不会经过代理
五、常见失效场景
5.1 自调用失效
这是最常见的 AOP 面试题。
1 | @Service |
this.methodB() 调用的是当前对象本身,不是代理对象,因此事务、缓存、日志等 AOP 增强都不会生效。
常见解决方式:
- 自注入代理对象
AopContext.currentProxy()- 拆分成两个 Bean
示例:
1 | @Service |
5.2 方法不可代理
以下情况常常导致增强失效:
- 目标方法是
private - 目标方法是
final - 目标类本身是
final - 方法不在代理暴露的调用路径中
5.3 非 Spring 容器对象
如果对象是手动 new 出来的,而不是从容器获取,就没有代理能力。
1 | UserService service = new UserService(); // 不会有 AOP |
5.4 调用时机太早
在这些阶段调用自身方法,通常也不会触发 AOP:
- 构造器
@PostConstruct- Bean 尚未完成代理替换之前的早期阶段
原因很简单:代理对象还没创建出来。
5.5 异常被吞掉
尤其是事务场景:
1 | @Around("...") |
这会带来两个问题:
- 上层以为方法成功执行了
- 事务看不到异常,可能不会回滚
正确做法通常是记录后继续抛出。
六、设计与性能建议
6.1 Pointcut 要尽量收敛
不建议:
1 | @Pointcut("execution(* com.example..*.*(..))") |
更推荐:
1 | @Pointcut("execution(* com.example..service.*.*(..))") |
原因:
- Bean 初始化时需要做匹配判断
- Pointcut 越宽,匹配成本越高
- 范围越大,误拦截风险越高
6.2 切面尽量无状态
Aspect 默认通常是单例 Bean,不应在实例字段中保存请求级数据:
1 | @Aspect |
如果确实需要线程隔离数据,用 ThreadLocal,并在结束后及时 remove()。
6.3 Advice 中避免重操作
比如:
- 无条件序列化全部参数
- 每次都做反射扫描
- 每次都创建大量临时对象
更稳妥的写法是:
- 先判断日志级别
- 能缓存的元数据提前缓存
- 只在必要时读取方法签名与注解
6.4 @Around 中保持异常语义
推荐模板:
1 | @Around("...") |
七、调试建议
调试 Spring AOP,建议重点盯住下面几个点:
7.1 看代理何时创建
可从这些类入手:
AnnotationAwareAspectJAutoProxyCreatorAbstractAutoProxyCreatorProxyFactoryDefaultAopProxyFactory
重点方法:
postProcessAfterInitialization()wrapIfNecessary()createProxy()
7.2 看拦截器链何时执行
重点方法:
ReflectiveMethodInvocation.proceed()JdkDynamicAopProxy.invoke()CglibAopProxy.DynamicAdvisedInterceptor.intercept()
7.3 先写最小可调试 Demo
建议最小化场景:
- 一个 Service
- 一个 Aspect
- 一个
@Around - 一个能触发自调用问题的方法
这样最容易看清代理创建和执行链。
八、面试高频快答
8.1 Spring AOP 的原理是什么
一句话版本:
Spring AOP 基于动态代理,在 Bean 初始化后由
BeanPostProcessor判断是否需要代理;方法调用时通过拦截器链执行增强逻辑。
8.2 Spring AOP 和 AspectJ 的区别
一句话版本:
Spring AOP 是运行时代理,只支持方法级连接点;AspectJ 是字节码织入,能力更强但复杂度更高。
8.3 @Transactional 为什么会失效
高频原因:
- 自调用
- 非 Spring Bean
- 方法不可代理
- 异常被吞
- 调用时机太早
8.4 @Around 和 @Before/@After 怎么选
- 只做前置/后置固定逻辑,用
@Before/@After - 需要控制是否执行目标方法、修改入参、改返回值、统一异常处理,用
@Around
8.5 多个切面怎么控制顺序
通过:
@OrderOrdered
数字越小,越先进入;退出时顺序相反。
九、延伸阅读
AbstractAutoProxyCreatorAnnotationAwareAspectJAutoProxyCreatorDefaultAopProxyFactoryReflectiveMethodInvocationTransactionInterceptor
本文档编写于 2026-07-09,版本上下文为
Spring Boot 3.3.9 + Java 17 + Spring Framework 6.2.11。
