← 返回文章

Spring AOP

发布于 2026/10/21

1. 完整回答

Spring AOP 是 Spring 提供的面向切面编程机制,主要用于把日志记录、事务管理、权限校验、性能监控等通用逻辑从业务代码中抽离出来,实现统一处理,减少重复代码,降低业务模块之间的耦合。

它的核心原理是动态代理。Spring 在容器初始化过程中,通过 Bean 后置处理器识别需要增强的 Bean,并为其创建代理对象。外部调用业务方法时,会先经过代理对象,由代理执行匹配的通知逻辑,再调用目标方法。

Spring AOP 主要支持两种代理方式:JDK 动态代理和 CGLIB 动态代理。JDK 动态代理基于接口生成代理对象,CGLIB 通过生成目标类的子类实现方法拦截。Spring Framework 默认优先使用 JDK 动态代理,而 Spring Boot 自动配置默认使用 CGLIB。

实际开发中,可以通过 @Aspect 定义切面,通过切点表达式指定增强范围,再使用 @Before、@Around 等注解定义增强行为。

Spring AOP 的特点是对业务代码侵入小、横切逻辑复用性高,并且与 Spring 容器集成紧密。

例如,@Transactional 就是典型的应用。调用事务方法时,代理负责在方法执行前开启或加入事务,在执行结束后根据结果提交或回滚。

不过,Spring AOP 基于代理机制,主要支持 Spring Bean 的方法执行,因此存在同类方法自调用绕过代理、部分方法无法增强等限制。

2. 核心原理与关键概念

2.1 核心原理

Spring AOP 的实现分为代理对象创建和方法调用拦截两个阶段。

第一阶段:创建代理对象

Bean 实例化与初始化 → Bean 后置处理器检查切面配置 → 匹配适用的切点与通知 → 创建 JDK 或 CGLIB 代理 → 将代理对象交给容器使用。

其中,自动代理创建器基于 BeanPostProcessor 机制工作。对于需要增强的 Bean,Spring 会组织对应的通知和拦截器,并创建代理对象。

第二阶段:执行代理方法

外部方法调用

调用 Spring 容器提供的 Bean

AOP 代理对象

根据切点匹配并执行拦截器链

前置增强逻辑

日志、权限检查等

目标方法执行

返回或异常处理

后置通知、异常通知等

向调用方返回结果或抛出异常

流程示意:具体通知的执行取决于通知类型、匹配结果及执行顺序。

多个通知同时匹配时,Spring 会形成拦截器链,按照顺序执行。环绕通知可以在目标方法前后插入逻辑,还可以决定是否继续执行后续调用。

2.2 关键概念

概念 含义
切面(Aspect) 封装某类通用增强逻辑,例如日志切面
连接点(Join Point) 可以应用增强的位置,Spring AOP 中对应方法执行
切点(Pointcut) 通过表达式筛选需要增强的方法
通知(Advice) 在匹配的方法执行前后运行的增强逻辑
目标对象(Target) 需要被增强的原始业务对象
代理对象(Proxy) Spring 创建的对象,负责执行增强逻辑并调用目标对象
增强器(Advisor) 将切点与通知组合起来,供代理进行匹配和执行

其中,切点负责确定增强哪些方法,通知负责确定执行什么增强逻辑,代理负责真正执行这些逻辑。

3. 高频面试追问

3.1 Spring AOP 为什么使用动态代理?JDK 和 CGLIB 如何选择?

动态代理可以在不修改目标类业务代码的情况下,为方法增加统一逻辑。

JDK 动态代理通过实现目标接口生成代理类,方法调用会进入 InvocationHandler,再由代理执行增强逻辑。

CGLIB 通过运行时生成目标类的子类,利用方法重写实现拦截,因此不要求业务类实现接口。

选择方式取决于框架配置:

  • Spring Framework 默认配置:有接口优先使用 JDK 动态代理,没有接口使用 CGLIB。

  • Spring Boot AOP 自动配置:默认使用 CGLIB,可以设置 spring.aop.proxy-target-class=false 改为优先使用 JDK 动态代理。

两种方式都能够实现 AOP,主要区别体现在接口依赖、代理类型和方法增强范围上。

3.2 Spring AOP 的代理对象是在什么时候创建的?

通常是在 Bean 创建过程中,通过 Bean 后置处理器自动生成的。

Spring 会利用自动代理创建器检查 Bean 是否存在适用的切点和通知。

如果需要增强,就把相关通知组织成拦截器链,并创建代理对象;如果没有适用的增强,通常直接使用原始 Bean。

后续从容器获取这个 Bean 时,得到的就是对应的代理对象。

因此,AOP 与 Spring Bean 生命周期密切相关,普通 new 出来的对象不会自动获得 Spring AOP 增强。

3.3 Spring AOP 有哪些通知类型?环绕通知有什么特殊之处?

Spring AOP 提供五种常见通知,主要区别在于执行时机和对方法执行流程的控制能力。

通知 执行时机
@Before 目标方法执行前
@AfterReturning 目标方法正常返回后
@AfterThrowing 目标方法抛出异常后
@After 目标方法结束后,无论正常还是异常
@Around 包围目标方法执行过程,可以控制是否继续执行

其中,@Around 的控制能力最强。它通过 ProceedingJoinPoint.proceed() 继续执行后续拦截器和目标方法。

如果没有调用 proceed(),后续执行就可能被跳过;如果需要改变最终返回结果,也可以通过环绕通知完成。

例如,统计方法执行耗时,就可以在调用 proceed() 前记录开始时间,并在 finally 中计算耗时,确保异常情况下也能记录。

继续追问:@AfterReturning 能修改方法返回值吗?

它可以获取正常返回的结果,也可以修改可变返回对象的内部状态,但不能通过重新赋值通知参数来替换调用方看到的返回对象。需要替换返回结果时,通常使用 @Around。

3.4 Spring AOP 如何指定哪些方法需要增强?

主要通过切点表达式匹配方法,常用的是 execution 和 @annotation。

execution 根据方法签名匹配,可以限定包名、类名、方法名、参数等。

例如:

execution(* com.example.service..*.*(..))

表示匹配 com.example.service 包及其子包中相关类的方法执行。

@annotation 则根据方法上的注解进行匹配,适合自定义注解场景。

例如,通过 @annotation 匹配标注了 @OperationLog 的方法,就能只对指定业务操作记录日志。

实际开发中,通用范围控制适合使用 execution,需要业务主动标记的场景适合使用 @annotation。

另外,使用注解方式声明切面时,@Aspect 用来标识切面,通常还需要 @Component 或 @Bean 将其注册到 Spring 容器。

3.5 为什么同一个类内部调用方法,AOP 可能不生效?

因为内部方法调用会绕过代理对象。

假设一个类包含 methodA() 和 methodB(),其中 methodB() 配置了事务。

如果外部通过代理调用 methodA(),而 methodA() 内部通过 this.methodB() 调用另一个方法,那么这个内部调用直接发生在目标对象上,不会重新经过 Spring AOP 代理。

因此,methodB() 自身配置的事务通知不会被触发。

常见解决方式是:

  1. 将两个方法拆分到不同的 Spring Bean,通过依赖注入调用。

  2. 通过注入自身代理引用的方式调用。

  3. 需要更完整的方法织入能力时,使用 AspectJ。

通常优先选择拆分 Bean,因为调用关系更清晰,对代理实现的依赖也更少。

继续追问:如果 methodA() 已经开启事务,内部调用 methodB() 还在事务中吗?

如果 methodA() 已经通过代理开启事务,methodB() 的数据库操作通常仍然参与这个已有事务。

但 methodB() 上单独声明的事务配置,例如 REQUIRES_NEW,不会因为这次内部调用而生效。

3.6 @Transactional 为什么有时不生效?

常见原因包括没有经过代理、方法无法被代理拦截,以及异常处理不符合事务回滚规则。

主要有以下几种情况:

  1. 同类内部调用:绕过代理,无法触发被调用方法的事务通知。

  2. 对象没有交给 Spring 管理:普通 new 创建的对象没有获得事务代理。

  3. 方法增强受限:例如 private 方法无法通过代理拦截,CGLIB 也无法增强 final 方法。

  4. 异常被内部捕获并正常返回:事务拦截器没有感知到需要回滚的异常。

  5. 异常类型不符合默认回滚规则:默认对 RuntimeException 和 Error 回滚,受检异常通常需要通过 rollbackFor 指定。

另外,事务方法通常建议声明为 public。从 Spring Framework 6.0 开始,基于类的事务代理默认还支持符合条件的 protected 和包可见方法,而 JDK 接口代理要求事务方法是接口中的 public 方法。

排查时,首先确认调用是否经过代理对象,再检查事务配置和异常传播情况。

3.7 Spring AOP 能增强所有方法吗?

不能。Spring AOP 的能力受到代理机制和方法可访问性的限制。

主要限制包括:

  • private 方法无法通过常规 Spring AOP 代理增强。

  • CGLIB 无法代理 final 类,也无法增强 final 方法。

  • static 方法和构造方法不属于 Spring AOP 常规代理拦截范围。

  • 普通非 Spring Bean 不会自动获得 Spring AOP 增强。

  • 类内部直接调用其他方法时,相关调用不会重新经过代理。

需要注意,JDK 动态代理可以通过接口拦截目标类中的 final 方法调用,因为它不依赖继承目标类。

如果需要对构造方法、字段访问或类内部调用进行更深入的增强,可以考虑 AspectJ 字节码织入。

3.8 多个切面同时增强一个方法,执行顺序怎么确定?

可以通过 @Order 指定不同切面的优先级,数值越小,优先级越高。

例如,切面 A 的优先级为 1,切面 B 的优先级为 2,两个切面都使用环绕通知。

正常情况下,执行顺序是:

A 前置逻辑 → B 前置逻辑 → 目标方法 → B 后置逻辑 → A 后置逻辑。

这是因为高优先级切面通常位于调用链的外层,先进入、后退出。

如果没有明确配置优先级,不应依赖不同切面之间的默认执行顺序。

实际开发中,如果日志、事务和权限校验等逻辑对先后顺序有要求,就需要明确设计通知顺序,同时注意异常可能被外层环绕通知处理。

3.9 Spring AOP 和 AspectJ 有什么区别?什么时候使用 AspectJ?

Spring AOP 适合 Spring Bean 的方法级增强,AspectJ 提供更完整的字节码织入能力。

Spring AOP 在运行时生成代理对象,主要拦截 Spring Bean 的方法执行,配置和使用比较方便。

AspectJ 可以在编译期或类加载期将增强逻辑织入字节码,支持更丰富的连接点,例如构造方法执行、字段访问等。

另外,Spring AOP 虽然使用 @Aspect、@Around 等 AspectJ 风格的注解和切点表达式,但默认仍然采用 Spring 自身的动态代理机制。

对于事务、日志和一般方法监控,Spring AOP 通常已经足够。如果要处理代理无法覆盖的内部调用或构造方法等场景,可以考虑 AspectJ。

3.10 项目中如何使用 Spring AOP 实现统一日志和耗时统计?

可以通过自定义注解配合环绕通知,对需要监控的业务方法统一增强。

具体流程是:

定义自定义注解 → 编写 @Aspect 切面 → 使用 @annotation 匹配方法 → 在环绕通知中记录时间与执行结果 → 将监控信息输出到日志或指标系统。

例如,订单创建和支付处理方法都需要统计执行耗时,可以统一使用切面处理,避免每个方法重复编写计时代码。

实现时要注意三个问题:

第一,通过 try-finally 记录耗时,确保方法抛出异常时也能统计。

第二,正常返回结果和业务异常都应按原有语义继续传递,避免监控逻辑影响业务行为。

第三,日志中要避免直接输出密码、令牌等敏感数据,切点范围也要适当控制,减少不必要的性能开销。

对于通用方法监控,AOP 比逐个方法添加重复代码更容易维护。

4. 易混淆概念对比

4.1 JDK 动态代理与 CGLIB 动态代理

对比维度 JDK 动态代理 CGLIB 动态代理
实现原理 生成实现接口的代理类 生成继承目标类的代理子类
接口要求 需要接口 不需要接口
增强范围 代理接口中的方法 可增强符合条件的类方法
final 限制 可代理 final 目标类的接口方法 无法代理 final 类或增强 final 方法
代理对象类型 实现目标接口 继承目标类

补充:Spring Framework 的常规默认策略是有接口优先 JDK,Spring Boot AOP 自动配置默认使用 CGLIB,两者需要结合实际配置区分。

4.2 @After、@AfterReturning 与 @AfterThrowing

通知 正常返回 抛出异常 典型用途
@After 执行 执行 通用收尾处理
@AfterReturning 执行 不执行 获取正常返回结果
@AfterThrowing 不执行 执行 异常记录与分析

@After 具有类似 finally 的语义。

如果需要在同一段逻辑中控制方法执行、返回结果和异常处理,可以使用 @Around。

4.3 Spring AOP 与 AspectJ

对比维度 Spring AOP AspectJ
实现方式 运行时动态代理 字节码织入
增强时机 运行时创建代理 编译期或类加载期
作用范围 主要针对 Spring Bean 可作用于更广泛的对象
连接点 方法执行 方法、构造方法、字段访问等
内部方法调用 通常绕过代理 可以通过织入增强
使用复杂度 较低 相对较高

4.4 AOP、Filter 与 HandlerInterceptor

对比维度 Spring AOP Filter HandlerInterceptor
所属层次 Spring 方法调用层 Servlet 过滤器链 Spring MVC 请求处理层
拦截对象 匹配的 Bean 方法 HTTP 请求与响应 控制器请求处理流程
作用范围 不限于 Web 请求 Servlet 请求链 Spring MVC 请求
常见用途 事务、方法日志、耗时统计 编码处理、请求包装、过滤 登录检查、接口请求预处理

例如,统计某个 Service 方法的执行耗时适合 AOP;统一处理进入 Web 应用的 HTTP 请求适合 Filter;需要结合控制器处理流程进行拦截时,适合 HandlerInterceptor。

三者可以配合使用,具体选择取决于拦截发生在哪一层。

5. 总结

Spring AOP 是 Spring 提供的面向切面编程机制,主要解决事务、日志、权限校验等通用逻辑分散在业务代码中的问题。底层通过 JDK 动态代理或 CGLIB 代理实现增强,Spring 在 Bean 创建过程中生成代理对象,外部方法调用经过代理后,按照切点匹配结果执行对应的通知和目标方法。它的主要特点是业务侵入小、通用逻辑复用性高,并且与 Spring 容器集成紧密。需要注意的是,Spring AOP 主要增强 Spring Bean 的方法执行,内部自调用会绕过代理,CGLIB 也存在 final 方法无法增强等限制。对于一般企业应用中的事务管理和方法级增强,Spring AOP 通常就能够满足需求。