Spring AOP核心机制:代理与拦截揭秘

在 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 的核心机制可归纳为三点:

  1. 基于动态代理(JDK / CGLIB)实现运行时织入;
  2. 代理对象在 BeanPostProcessor.postProcessAfterInitialization() 阶段创建
  3. 只有通过代理对象的方法调用,才会进入拦截器链并执行切面逻辑

理解这三点,就能解释包括 @Transactional 失效在内的大多数 AOP 相关面试问题。


欢迎在评论区交流你在 AOP 或 Spring 面试中遇到的问题。

如果你觉得本文帮你理清了原理脉络,欢迎点赞或收藏,方便面试前快速回顾。

相关推荐
Ralph_Y2 小时前
C++网络:一
开发语言·网络·c++
代码探秘者2 小时前
【Redis】分布式锁深度解析:实现、可重入、主从一致性与强一致方案
java·数据库·redis·分布式·缓存·面试
Hui Baby2 小时前
浅谈MCP原理
开发语言
2345VOR2 小时前
【QT的pyside6开发使用】
开发语言·qt
Ronin3053 小时前
【Qt常用控件】控件概述和QWidget 核心属性
开发语言·qt·常用控件·qwidget核心属性
故事和你913 小时前
sdut-程序设计基础Ⅰ-实验二选择结构(1-8)
大数据·开发语言·数据结构·c++·算法·优化·编译原理
JAVA学习通3 小时前
InnoDB 存储引擎
java·数据库·mysql
努力学算法的蒟蒻3 小时前
day106(3.7)——leetcode面试经典150
算法·leetcode·面试