Spring中AOP的简介和基本使用,SpringBoot使用AOP
- 发布时间:2026-03-26 07:15:15
- 本文热度:浏览 3 赞 0 评论 0
- 文章标签: Spring AOP SpringBoot Java
- 全文共1字,阅读约需1分钟
在企业级 Java 开发中,横切关注点几乎无处不在。日志记录、权限校验、事务控制、性能监控、异常统一处理、接口幂等、审计追踪,这些能力往往会分散在大量业务方法周围。如果直接把这些通用逻辑写进每一个 Service、Controller 或 Repository 中,代码会迅速出现重复、耦合和难以维护的问题。
AOP,Aspect Oriented Programming,面向切面编程,就是 Spring 为解决这类问题提供的重要能力。它并不替代 OOP,而是在面向对象的基础上,对“横向重复逻辑”做统一抽取和织入。对于 Spring 体系来说,AOP 既是一个常用的开发工具,也是理解事务、缓存、异步、权限等高级特性的基础。
一、为什么需要 AOP
先看一个非常常见的场景:订单服务中的方法都需要打印入参、记录执行耗时,并在发生异常时做统一日志输出。
传统写法通常是这样:
public class OrderService {
public void createOrder(String userId, Long productId) {
long start = System.currentTimeMillis();
try {
System.out.println("createOrder start, userId=" + userId + ", productId=" + productId);
// 核心业务逻辑
System.out.println("createOrder success");
} catch (Exception e) {
System.out.println("createOrder error: " + e.getMessage());
throw e;
} finally {
System.out.println("cost=" + (System.currentTimeMillis() - start));
}
}
public void cancelOrder(Long orderId) {
long start = System.currentTimeMillis();
try {
System.out.println("cancelOrder start, orderId=" + orderId);
// 核心业务逻辑
System.out.println("cancelOrder success");
} catch (Exception e) {
System.out.println("cancelOrder error: " + e.getMessage());
throw e;
} finally {
System.out.println("cost=" + (System.currentTimeMillis() - start));
}
}
}
这种方式的问题很明显:
- 业务逻辑与非业务逻辑混杂在一起,核心代码不够清晰。
- 大量重复代码分散在不同方法中,维护成本高。
- 如果日志格式、统计方式或异常处理规则需要变更,就要修改很多位置。
- 这些通用逻辑很难统一治理,容易出现不一致。
AOP 的核心价值就是:把这些分散在各处的公共行为抽离出来,在不侵入业务代码的情况下,统一织入到目标方法的执行过程里。
二、AOP 的核心思想
AOP 的本质可以理解为:在程序运行到某些特定位置时,自动执行一段预定义的增强逻辑。
例如:
- 方法执行前打印日志
- 方法执行后记录结果
- 方法抛出异常时进行统一处理
- 方法执行前后统计耗时
- 对特定注解的方法做权限控制
- 对接口请求做防重复提交校验
这类能力都不是某一个业务模块独有的,而是横跨多个模块,因此被称为“横切关注点”。
面向切面编程中的“切面”,就是把这些横切逻辑封装起来,在指定的连接点进行增强。
三、Spring AOP 中的重要概念
理解 Spring AOP,必须先掌握几个核心术语。
1. Aspect(切面)
切面是对横切逻辑的封装,通常由以下两部分组成:
- 在什么位置生效
- 生效时执行什么逻辑
在 Spring 中,通常通过 @Aspect 标记一个类为切面类。
2. JoinPoint(连接点)
连接点指程序执行过程中的某个点,例如方法调用、方法执行、异常抛出等。
但在 Spring AOP 中,连接点主要限定为方法执行。这点非常重要。Spring AOP 并不是完整的 AspectJ,它是基于代理机制实现的,因此主要作用在 Bean 方法级别。
3. Pointcut(切点)
切点用于定义:哪些连接点需要被拦截。
它本质上是一个匹配规则,例如:
- 拦截某个包下所有 Service 方法
- 拦截加了某个注解的方法
- 拦截某个类中的特定方法
4. Advice(通知)
通知表示在切点匹配成功后,需要执行的增强逻辑。常见通知类型包括:
@Before:方法执行前通知@After:方法执行后通知,不管是否异常都会执行@AfterReturning:方法正常返回后通知@AfterThrowing:方法抛出异常后通知@Around:环绕通知,最强大,可以控制目标方法是否执行、何时执行、如何返回
5. Target(目标对象)
被代理、被增强的原始业务对象。
6. Proxy(代理对象)
Spring AOP 不是直接修改目标对象,而是为目标对象创建一个代理对象。外部调用通常先进入代理对象,再由代理对象决定是否执行增强逻辑以及何时调用原始方法。
7. Weaving(织入)
把切面逻辑应用到目标对象并生成代理对象的过程,称为织入。
四、Spring AOP 的实现原理
Spring AOP 的底层实现核心是动态代理。Spring 在容器启动过程中,会为符合条件的 Bean 创建代理对象,调用代理对象时触发切面逻辑。
Spring 主要支持两种代理方式:
1. JDK 动态代理
当目标对象实现了接口时,Spring 默认优先使用 JDK 动态代理。
特点:
- 基于接口生成代理对象
- 代理对象类型是接口的实现类
- 只能代理接口中的方法
2. CGLIB 动态代理
当目标对象没有实现接口,或者显式要求使用类代理时,Spring 会使用 CGLIB。
特点:
- 基于继承生成子类代理
- 可以代理没有实现接口的类
- 不能代理
final类 - 不能代理
final方法,因为子类无法重写
3. Spring Boot 中的默认情况
在较新的 Spring Boot 版本中,通常默认启用基于类的代理,也就是 CGLIB 代理方式更常见。但这依赖具体配置和 Spring 版本。生产中不要只凭印象判断,应当明确了解项目是否设置了:
spring.aop.proxy-target-class=true
true:优先使用 CGLIBfalse:如果有接口则使用 JDK 动态代理
4. 为什么 AOP 对内部方法调用不生效
这是 Spring AOP 使用中最常见的坑之一。
例如:
@Service
public class UserService {
public void methodA() {
methodB();
}
public void methodB() {
// 假设这里有 AOP 切面
}
}
当外部调用 methodA() 时,methodA() 内部直接调用 methodB(),这属于对象内部的 this.methodB() 调用,不会经过 Spring 代理对象,因此 methodB() 上的 AOP 增强不会生效。
这也是为什么很多人会发现:
@Transactional不生效@Async不生效- 自定义 AOP 注解不生效
本质原因通常都是:内部调用绕过了代理对象。
五、Spring AOP 的使用场景
AOP 并不是为了炫技,而是为了解决具体问题。常见场景包括:
1. 日志记录
在接口层或服务层统一记录方法入参、返回值、耗时、异常信息。
2. 事务管理
Spring 的声明式事务本质上就是基于 AOP 实现的。@Transactional 会在方法执行前后织入事务开启、提交、回滚逻辑。
3. 权限校验
对某些方法统一做角色判断、权限认证、数据范围过滤。
4. 性能监控
统计接口耗时、方法耗时,识别慢调用。
5. 异常统一处理与审计
对关键业务操作做审计日志记录,或者捕获异常进行统一上报。
6. 缓存、重试、幂等
很多通用治理能力都可以通过 AOP 配合注解来实现。
六、Spring AOP 的通知类型详解
1. @Before
在目标方法执行前执行,适合做参数检查、前置日志记录。
@Before("execution(* com.example.service..*.*(..))")
public void before() {
System.out.println("方法执行前");
}
2. @After
无论目标方法正常结束还是抛出异常,都会执行。
@After("execution(* com.example.service..*.*(..))")
public void after() {
System.out.println("方法执行后");
}
它类似 finally 语义。
3. @AfterReturning
目标方法正常返回后执行,可以拿到返回值。
@AfterReturning(value = "execution(* com.example.service..*.*(..))", returning = "result")
public void afterReturning(Object result) {
System.out.println("返回值: " + result);
}
4. @AfterThrowing
目标方法抛出异常后执行,可以拿到异常对象。
@AfterThrowing(value = "execution(* com.example.service..*.*(..))", throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("异常信息: " + e.getMessage());
}
5. @Around
环绕通知最常用,也最强大。它可以:
- 决定是否执行目标方法
- 在目标方法前后执行逻辑
- 修改返回值
- 捕获异常
- 统计耗时
@Around("execution(* com.example.service..*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("前置逻辑");
Object result = joinPoint.proceed();
System.out.println("后置逻辑");
return result;
}
实际项目中,日志统计、接口埋点、方法耗时监控往往首选 @Around。
七、切点表达式 execution 详解
Spring AOP 最经典的切点表达式写法就是 execution(...)。
通用格式如下:
execution(访问修饰符 返回值类型 包名.类名.方法名(参数列表))
可使用通配符。
1. 常见通配符
*:匹配任意字符,但通常不跨层级..:匹配任意层级包路径或任意参数():匹配无参方法(..):匹配任意参数方法
2. 典型示例
匹配某个类下所有方法:
execution(* com.example.service.UserService.*(..))
匹配某个包下所有类的所有方法:
execution(* com.example.service.*.*(..))
匹配某个包及其子包下所有方法:
execution(* com.example.service..*.*(..))
匹配所有返回值为 String 的方法:
execution(String com.example.service..*.*(..))
匹配以 save 开头的方法:
execution(* com.example.service..*.save*(..))
3. 切点复用
实际开发中,切点通常会单独提取,便于复用。
@Pointcut("execution(* com.example.service..*.*(..))")
public void servicePointcut() {
}
然后在通知中使用:
@Before("servicePointcut()")
public void before() {
System.out.println("before...");
}
八、Spring Boot 中 AOP 的基本使用
下面通过一个完整示例说明 Spring Boot 中如何使用 AOP。
1. 引入依赖
Maven 项目中引入 AOP 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
这个 starter 会引入 Spring AOP 相关能力,通常已经足够。
2. 创建业务类
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public String createOrder(String userId, Long productId) {
System.out.println("执行创建订单逻辑");
return "ORDER_" + System.currentTimeMillis();
}
public String cancelOrder(Long orderId) {
System.out.println("执行取消订单逻辑");
return "SUCCESS";
}
public String queryOrder(Long orderId) {
System.out.println("执行查询订单逻辑");
if (orderId == null) {
throw new IllegalArgumentException("orderId不能为空");
}
return "ORDER_INFO_" + orderId;
}
}
3. 创建切面类
package com.example.demo.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Slf4j
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.example.demo.service..*.*(..))")
public void serviceMethods() {
}
@Before("serviceMethods()")
public void doBefore(JoinPoint joinPoint) {
log.info("before method={}, args={}",
joinPoint.getSignature().toShortString(),
Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(value = "serviceMethods()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
log.info("afterReturning method={}, result={}",
joinPoint.getSignature().toShortString(),
result);
}
@AfterThrowing(value = "serviceMethods()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
log.error("afterThrowing method={}, error={}",
joinPoint.getSignature().toShortString(),
e.getMessage(), e);
}
@Around("serviceMethods()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long cost = System.currentTimeMillis() - start;
log.info("around method={}, cost={}ms",
joinPoint.getSignature().toShortString(),
cost);
return result;
} catch (Throwable e) {
long cost = System.currentTimeMillis() - start;
log.error("around method={}, cost={}ms, exception={}",
joinPoint.getSignature().toShortString(),
cost,
e.getMessage(), e);
throw e;
}
}
}
4. 启动类
在 Spring Boot 中,只要引入了 spring-boot-starter-aop 并且切面类被扫描到,通常无需额外开启配置即可生效。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AopDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AopDemoApplication.class, args);
}
}
5. 编写 Controller 进行测试
package com.example.demo.controller;
import com.example.demo.service.OrderService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/order")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping("/create")
public String create(@RequestParam String userId, @RequestParam Long productId) {
return orderService.createOrder(userId, productId);
}
@PostMapping("/cancel")
public String cancel(@RequestParam Long orderId) {
return orderService.cancelOrder(orderId);
}
@GetMapping("/query")
public String query(@RequestParam(required = false) Long orderId) {
return orderService.queryOrder(orderId);
}
}
访问接口后,就可以看到切面中记录的方法执行日志、返回值和异常信息。
九、基于注解的 AOP 实战方式
实际项目中,直接按包路径拦截虽然简单,但灵活性有限。很多场景更适合通过自定义注解实现。
例如:只对标注了某个注解的方法进行操作日志记录。
1. 定义自定义注解
package com.example.demo.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
String value() default "";
}
2. 在业务方法上使用注解
package com.example.demo.service;
import com.example.demo.annotation.OperationLog;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@OperationLog("创建商品")
public String createProduct(String name) {
System.out.println("执行创建商品逻辑");
return "success";
}
@OperationLog("删除商品")
public String deleteProduct(Long id) {
System.out.println("执行删除商品逻辑");
return "success";
}
}
3. 编写注解切面
package com.example.demo.aspect;
import com.example.demo.annotation.OperationLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Slf4j
@Aspect
@Component
public class OperationLogAspect {
@Pointcut("@annotation(com.example.demo.annotation.OperationLog)")
public void operationLogPointcut() {
}
@Around("operationLogPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLog operationLog = method.getAnnotation(OperationLog.class);
String operationName = operationLog.value();
log.info("operation start, name={}, method={}, args={}",
operationName,
signature.toShortString(),
Arrays.toString(joinPoint.getArgs()));
try {
Object result = joinPoint.proceed();
log.info("operation success, name={}, result={}, cost={}ms",
operationName,
result,
System.currentTimeMillis() - start);
return result;
} catch (Throwable e) {
log.error("operation fail, name={}, cost={}ms, error={}",
operationName,
System.currentTimeMillis() - start,
e.getMessage(), e);
throw e;
}
}
}
这种方式的优点非常明显:
- 拦截范围精确
- 业务可读性强
- 使用方式简单
- 便于后期扩展
在后台管理系统、审计系统、运营平台中,这种方式很常见。
十、Spring AOP 与 AspectJ 的关系
很多初学者容易把 Spring AOP 和 AspectJ 混为一谈,实际上两者有关联,但并不等价。
1. Spring AOP
- Spring 提供的 AOP 实现
- 基于动态代理
- 主要支持方法级别增强
- 使用简单,与 Spring IoC 容器整合紧密
- 适合绝大多数企业应用场景
2. AspectJ
- 更完整的 AOP 方案
- 支持编译期织入、类加载期织入
- 不局限于 Spring Bean,也可以增强字段、构造器等更多连接点
- 功能更强,但复杂度更高
3. 二者关系
Spring AOP 使用了 AspectJ 的注解风格,例如:
@Aspect@Pointcut@Before@After@Around
但这并不意味着 Spring AOP 就等于完整的 AspectJ。多数 Spring Boot 项目中使用的其实是基于 Spring 代理机制的 AOP。
十一、Spring Boot 中 AOP 的典型实战场景
1. 接口访问日志
在 Controller 层统一记录请求 URL、请求参数、IP、耗时等信息。
这种方式适合:
- 排查线上问题
- 记录慢接口
- 构建访问审计日志
但需要注意,Controller 层参数中可能包含大对象、文件流、敏感字段,不应无脑全部序列化。
2. Service 层操作审计
例如创建订单、退款、发货、审批等关键动作,需要写审计日志。
最佳实践通常是:
- 使用注解明确标识关键操作
- 切面中收集操作类型、操作人、业务对象标识、执行结果
- 异步写入日志库或消息队列
3. 统一性能统计
通过 AOP 对特定包或特定注解的方法统计耗时,将慢调用写入日志或监控系统。
例如:
- 超过 200ms 记为 warn
- 超过 1000ms 记为 error
- 上报到 Prometheus、SkyWalking 或自研监控平台
4. 参数合法性扩展校验
虽然参数校验通常优先使用 javax.validation 或 jakarta.validation,但在一些复杂业务规则中,也会借助 AOP 做跨参数、跨对象的统一检查。
5. 幂等处理
对某些接口增加防重复提交能力,例如:
- 基于请求 token
- 基于 Redis 分布式锁
- 基于业务唯一键
AOP 可以负责拦截带注解的方法,统一进行幂等判断。
十二、Spring AOP 常见问题与注意事项
1. 只能增强 Spring 容器管理的 Bean
AOP 的前提是对象由 Spring 容器创建并管理。如果对象是通过 new 手动创建的,那么它不会被 Spring 代理,切面自然不会生效。
错误示例:
UserService userService = new UserService();
userService.test();
这种方式不会经过 Spring 容器,也不会触发 AOP。
2. 内部调用不生效
前面已经提到,这是最容易踩坑的地方。解决思路通常有:
- 把被增强方法拆到另一个 Bean 中
- 通过 Spring 容器获取代理对象后再调用
- 使用
AopContext.currentProxy(),但这会增加耦合,一般不优先推荐
3. private 方法通常无法被代理增强
Spring AOP 基于代理,而代理关注的是对外可调用的方法。通常:
private方法不能被代理增强final方法不能被 CGLIB 有效重写增强static方法也不属于典型代理增强对象
因此,AOP 增强方法通常应设计为 public,这也是事务方法普遍建议声明为 public 的原因之一。
4. 不要在切面中做过重逻辑
切面的定位是通用横切逻辑,而不是承载复杂业务。如果在切面中做大量数据库访问、远程调用、复杂计算,会带来:
- 性能问题
- 调试困难
- 职责混乱
特别是日志切面中,如果同步写数据库,很容易拖慢主流程。更合理的方式是异步化处理。
5. 切点范围不要过大
如果写出类似这样的表达式:
execution(* com.example..*.*(..))
虽然方便,但拦截范围过大,可能导致:
- 无关方法被增强
- 日志量爆炸
- 性能损耗加大
- 问题定位困难
切点表达式应尽量做到“精确而清晰”。
6. 注意敏感信息脱敏
日志切面尤其要注意:
- 密码
- 身份证号
- 手机号
- 银行卡号
- Token
- 密钥类字段
不能直接完整打印。应在切面中做脱敏处理,或者对敏感字段进行过滤。
7. AOP 不是万能的
AOP 擅长处理通用横切逻辑,但不适合替代正常的业务建模。凡是具有明确业务语义的流程,仍然应在业务层显式表达,而不是全部藏进切面中。否则代码表面简洁,实际可维护性反而下降。
十三、Spring AOP 与事务的关系
理解 AOP 后,再看 Spring 事务会更清晰。
例如:
@Transactional
public void createOrder() {
// 数据库操作
}
@Transactional 并不是编译器自动识别的语法糖,它本质上是 Spring 在方法调用前后织入了事务处理逻辑:
- 方法进入前开启事务
- 方法正常完成则提交事务
- 方法抛出符合规则的异常则回滚事务
这正是 AOP 思想的典型体现。
也正因为事务基于 AOP,所以它同样受到代理机制的限制:
- 内部调用可能失效
- 非 Spring Bean 中不生效
private方法上通常不生效- 异常捕获后不继续抛出,可能导致事务不回滚
这些问题并不是事务本身的问题,而是 AOP 代理机制带来的行为边界。
十四、一个更贴近生产环境的 AOP 日志示例
在实际项目中,单纯打印方法名通常不够。一个更实用的日志切面通常会记录:
- 类名、方法名
- 方法参数
- 返回结果
- 执行耗时
- 异常堆栈
- traceId 或 requestId
- 当前登录用户
示例代码如下:
package com.example.demo.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Slf4j
@Aspect
@Component
public class MethodCostLogAspect {
@Pointcut("execution(* com.example.demo.service..*.*(..))")
public void pointcut() {
}
@Around("pointcut()")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Signature signature = joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object[] args = joinPoint.getArgs();
try {
log.info("invoke start, class={}, method={}, args={}",
className, methodName, Arrays.toString(args));
Object result = joinPoint.proceed();
log.info("invoke success, class={}, method={}, result={}, cost={}ms",
className, methodName, result, System.currentTimeMillis() - start);
return result;
} catch (Throwable e) {
log.error("invoke fail, class={}, method={}, args={}, cost={}ms, error={}",
className, methodName, Arrays.toString(args),
System.currentTimeMillis() - start, e.getMessage(), e);
throw e;
}
}
}
这个写法已经具备生产可用雏形,但上线前通常还要继续完善:
- 大对象截断
- 敏感字段脱敏
- 文件上传参数过滤
- 响应对象长度限制
- 异步日志落库
- 链路追踪字段透传
十五、Spring Boot 使用 AOP 的最佳实践
1. 优先用注解驱动,而不是粗暴按包全拦截
对于操作审计、权限校验、幂等控制等场景,推荐使用自定义注解进行精确切入。这样可维护性更高,语义也更明确。
2. 环绕通知是主力,但不要滥用
@Around 功能最完整,适合日志、耗时、异常统一处理等场景。但如果只是简单前置动作,用 @Before 更直接,语义也更清晰。
3. 切面逻辑保持轻量
切面只做通用逻辑协调,不承载复杂业务细节。尤其要避免在切面里堆大量分支判断。
4. 明确代理边界
开发时要清楚以下几个前提:
- 只有 Spring Bean 才能被增强
- 方法调用必须经过代理
- 内部调用可能失效
final/private/static等方法不适合做代理增强点
5. 与日志规范、链路追踪统一设计
如果系统接入了 SLF4J、Logback、MDC、SkyWalking、Zipkin、OpenTelemetry 等组件,AOP 日志方案要与整体可观测性体系配合,而不是单独零散打印。
6. 对返回值和参数序列化保持谨慎
不是所有对象都适合直接 toString() 或 JSON 序列化。例如:
- 文件流
- ServletRequest / ServletResponse
- 超大分页结果
- 循环引用对象
- 包含敏感数据的 DTO
因此,日志切面一般需要加入白名单、黑名单和长度限制机制。
十六、Spring AOP 与拦截器、过滤器的区别
很多人会把 AOP、拦截器、过滤器混用,但它们的定位不同。
1. Filter
- 属于 Servlet 规范
- 作用在 Web 请求入口
- 可以处理请求和响应
- 与 Spring 容器关系相对较弱
适合做:
- 编码处理
- 跨域处理
- 请求包装
- 通用 Web 层过滤
2. Interceptor
- 属于 Spring MVC
- 作用于 Controller 调用链
- 能拿到 Handler 信息
- 适合做接口权限、登录校验、请求日志
3. AOP
- 作用于 Spring Bean 方法
- 不局限于 Web 层
- 更适合 Service、Manager、Repository 等业务组件增强
适合做:
- Service 层日志
- 事务
- 审计
- 性能统计
- 自定义注解增强
三者并不是替代关系,而是不同层次的治理手段。
十七、一个完整认识:Spring Boot 中如何选择 AOP
在 Spring Boot 项目中,是否使用 AOP,关键不在于“能不能”,而在于“该不该”。
适合 AOP 的逻辑通常具备以下特征:
- 与具体业务流程弱相关
- 在多个模块中重复出现
- 逻辑较稳定,适合集中治理
- 具有统一入口和统一规则
例如:
- 方法调用日志
- 审计日志
- 幂等校验
- 接口限流的某些前置检查
- 事务处理
- 性能埋点
不适合 AOP 的逻辑通常是:
- 业务流程核心分支
- 强依赖上下文的业务决策
- 逻辑复杂且变化频繁
- 需要显式表达顺序和语义的业务操作
把所有逻辑都丢进切面,会让代码表面上很“优雅”,实际上难以排查和维护。
十八、总结
Spring AOP 的核心目标,是将日志、事务、审计、监控、权限等横切关注点从业务代码中抽离出来,通过代理机制在方法执行的特定时机统一织入,从而提升代码复用性、可维护性和治理能力。
在 Spring Boot 项目中使用 AOP 并不复杂,通常只需要:
- 引入
spring-boot-starter-aop - 定义切面类并使用
@Aspect - 编写切点表达式或自定义注解
- 在通知中实现增强逻辑
但真正要把 AOP 用好,关键不在语法,而在边界感。需要清楚它的适用场景、底层代理原理、内部调用失效原因,以及与事务、拦截器、过滤器之间的区别。只有在设计上把横切逻辑与业务逻辑分离清楚,AOP 才会成为提升系统质量的工具,而不是隐藏复杂度的黑盒。
对于 Spring 开发者来说,AOP 既是基础能力,也是通往更深入理解 Spring 事务、缓存、异步和框架扩展机制的重要入口。掌握它,不只是会写几个切面,更重要的是能够以更高的抽象层次组织系统中的通用行为。