在 Spring 开发中,我们经常使用
@Transactional、日志切面等功能,但很多人并不清楚它们是如何工作的。本质上,这些能力都建立在 Spring AOP 之上。
本文面向准备后端校招面试的同学,聚焦以下核心问题:
- AOP 的设计初衷是什么?为什么不能只用工具类?
- Spring AOP 如何通过动态代理实现"织入"?
- 代理对象在 Bean 生命周期中何时创建?
- 为什么
this.method()调用会导致 AOP 或事务失效?不展开 AspectJ 或字节码生成细节,只讲清 Spring AOP 在面试中最常被考察的核心机制。
一、什么是 AOP?
AOP(Aspect Oriented Programming),即 面向切面编程。
在实际开发中,一个业务方法往往不仅包含核心逻辑,还会混入一些通用逻辑,例如:
java
public void createOrder() {
// 记录日志
log.info("create order");
// 权限校验
checkPermission();
// 事务控制
beginTransaction();
orderService.save();
commitTransaction();
}
这些逻辑具有共同特点:
- 与核心业务无关
- 在多个方法中重复出现
AOP 的目标就是:将这类横切关注点(Cross-Cutting Concerns)从业务代码中抽离 ,形成统一的 切面(Aspect)。
常见应用场景包括:
- 日志记录
- 事务管理
- 权限校验
- 性能监控
二、Spring AOP 的核心概念
理解 Spring AOP 需要掌握几个基本术语:
| 概念 | 说明 |
|---|---|
| Aspect | 切面,封装横切逻辑的类 |
| Advice | 通知,具体的增强行为 |
| Pointcut | 切点,定义哪些方法会被拦截 |
| JoinPoint | 连接点,程序执行过程中的某个点(如方法调用) |
| Proxy | 代理对象,用于拦截方法调用 |
简单流程:
java
方法调用
↓
切点匹配
↓
代理对象拦截
↓
执行通知(Advice)
三、Spring AOP 的基本使用
在 Spring 中使用 AOP 非常直接。例如定义一个日志切面:
java
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.demo.service.*.*(..))")
public void before() {
System.out.println("before method");
}
}
当调用 orderService.createOrder() 时,Spring 会自动在方法执行前插入日志逻辑。
但关键问题是:
Spring 是如何把切面"织入"到业务方法中的?
答案是:代理对象。
四、Spring AOP 的实现机制
Spring AOP 的底层基于 动态代理,主要有两种方式:
| 代理方式 | 适用条件 |
|---|---|
| JDK 动态代理 | 目标类实现了接口 |
| CGLIB 代理 | 目标类没有接口 |
Spring 的默认策略是:
优先使用 JDK 动态代理;若无接口,则使用 CGLIB。
代理对象的调用结构如下:
java
客户端
↓
代理对象(Proxy)
↓
拦截器链(Interceptor Chain)
↓
目标对象(Target)
所有方法调用实际上都经过代理对象,从而有机会执行切面逻辑。
五、代理对象是什么时候创建的?
这是理解 Spring AOP 的关键。
很多人误以为代理在 Bean 实例化时就生成了,实际上:
代理是在 Bean 初始化完成之后,通过
BeanPostProcessor创建的。
具体生命周期流程:
java
1. 实例化 Bean
2. 依赖注入(populate)
3. 初始化(InitializingBean / init-method)
4. BeanPostProcessor.postProcessAfterInitialization()
5. → 此时判断是否需要创建代理
核心实现类是 AbstractAutoProxyCreator,其 postProcessAfterInitialization() 方法中调用:
wrapIfNecessary(bean, beanName)
若当前 Bean 匹配任意切点,则为其创建代理对象。
六、AOP 方法调用的执行流程
当客户端调用方法时,实际执行路径为:
客户端调用
↓
代理对象.invoke()
↓
ReflectiveMethodInvocation.proceed()
↓
遍历拦截器链(MethodInterceptor)
↓
依次执行 @Before、@Around 等 Advice
↓
最终调用目标方法
Spring 将多个通知封装为一个 拦截器链(Interceptor Chain),通过责任链模式依次执行。
七、为什么内部调用(this.method())会导致 AOP 失效?
这是校招面试高频问题。
@Service
public class OrderService {
public void methodA() {
this.methodB(); // 内部调用
}
@Transactional
public void methodB() {
// 数据库操作
}
}
此时 @Transactional 不会生效。
原因在于:
Spring AOP 是基于代理实现的,只有通过代理对象的方法调用才会触发切面逻辑 。
而
this.methodB()是直接调用目标对象的方法,绕过了代理,因此事务(或其他 AOP 逻辑)不会被织入。
解决思路包括:
- 将
methodB拆到另一个 Service 中 - 通过自注入获取代理实例(
@Autowired private OrderService self;)
八、Spring AOP 整体流程回顾
将上述环节串联起来:
1. 容器启动,扫描 @Aspect 注解
2. 注册 AbstractAutoProxyCreator(BeanPostProcessor)
3. Bean 初始化完成后,判断是否匹配切点
4. 若匹配,创建代理对象(JDK / CGLIB)
5. 后续方法调用通过代理进入拦截器链
6. 执行通知逻辑,再调用目标方法
九、面试回答参考
如果面试官问:"Spring AOP 是如何实现的?"
可参考如下回答:
Spring AOP 基于动态代理实现。在 Bean 初始化完成后,Spring 通过
BeanPostProcessor(具体是AbstractAutoProxyCreator)判断该 Bean 是否匹配切面定义。如果匹配,则为其创建代理对象(有接口用 JDK 代理,无接口用 CGLIB)。后续对该 Bean 的方法调用,实际是调用代理对象的方法,代理会将通知封装为拦截器链,在目标方法执行前后插入增强逻辑。
需要注意的是,只有通过代理对象的调用才会触发 AOP,内部调用(如
this.xxx())会绕过代理,导致切面失效。
十、总结
Spring AOP 的核心机制可归纳为三点:
- 基于动态代理(JDK / CGLIB)实现运行时织入;
- 代理对象在
BeanPostProcessor.postProcessAfterInitialization()阶段创建; - 只有通过代理对象的方法调用,才会进入拦截器链并执行切面逻辑。
理解这三点,就能解释包括 @Transactional 失效在内的大多数 AOP 相关面试问题。
欢迎在评论区交流你在 AOP 或 Spring 面试中遇到的问题。
如果你觉得本文帮你理清了原理脉络,欢迎点赞或收藏,方便面试前快速回顾。