本文系统总结 Spring AOP 六大核心注解,涵盖定义、语法、示例和最佳实践,并附完整可运行代码。
1. 注解速查表
| 注解 | 作用 | 执行时机 | 能否改参 | 典型场景 |
|---|---|---|---|---|
@Aspect |
声明切面类 | - | - | 标识切面 |
@Pointcut |
定义切入点 | - | - | 复用切点表达式 |
@Before |
前置通知 | 目标方法前 | ❌ 否 | 权限校验、日志记录 |
@After |
最终通知 | 目标方法后(finally) | ❌ 否 | 资源释放、清理动作 |
@AfterReturning |
返回后通知 | 成功返回后 | ❌ 否 | 结果缓存、成功日志 |
@AfterThrowing |
异常后通知 | 抛出异常后 | ❌ 否 | 异常告警、事务回滚 |
@Around |
环绕通知 | 包裹整个调用链 | ✅ 是 | 性能监控、事务管理、参数修改 |
@Order |
控制顺序 | - | - | 多切面执行优先级 |
2. 详细注解说明
2.1 @Aspect - 切面声明
作用:标识一个类为切面类,Spring 会自动扫描并织入通知。
使用方式:
@Aspect
@Component // 必须交予 Spring 管理
public class LoggingAspect {
// 通知方法...
}
要点:
-
必须与
@Component或@Configuration配合使用 -
一个类可包含多个
@Pointcut和多种通知
2.2 @Pointcut - 切入点定义
作用:定义可复用的切点表达式,避免在多个通知中重复编写。
语法:
@Pointcut("execution(修饰符? 返回类型 包名.类名.方法名(参数))")
public void methodName() {} // 方法体为空,仅作为标识
常用表达式:
-
execution(* com.example.service.*.*(..)):service 包下所有类的所有方法 -
execution(public * com.example.controller.*.*(..)):controller 包下所有 public 方法 -
@annotation(com.example.annotation.Log):标注了 @Log 注解的方法 -
args(java.io.Serializable):参数为 Serializable 的方法 -
within(com.example.service..*):service 包及其子包
示例:
@Pointcut("execution(* com.dycjr.xiakuan.report.support.indicator.controller.StatisticsController.*(..))")
public void statisticsControllerMethods() {}
@Pointcut("@annotation(com.dycjr.xiakuan.report.support.basePermission.controller.BasePermissionLimit)")
public void permissionLimitedMethods() {}
2.3 @Before - 前置通知
作用:在目标方法执行前运行,常用于权限校验、参数校验、日志记录。
特点:
-
无法修改参数 :只能读取,不能通过
args[i] = ...替换参数 -
无法阻止执行:只能通过抛异常中断流程
-
无返回值 :方法返回类型必须是
void
示例:
@Slf4j
@Aspect
@Component
public class SecurityAspect {
@Pointcut("@annotation(com.example.annotation.RequiresPermission)")
public void permissionCheck() {}
@Before("permissionCheck()")
public void checkPermission(JoinPoint joinPoint) {
// 1. 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getName();
// 2. 获取参数
Object[] args = joinPoint.getArgs();
Long userId = (Long) args[0];
// 3. 执行校验逻辑
if (!hasPermission(userId)) {
log.error("用户 {} 无权限访问方法 {}", userId, methodName);
throw new PermissionDeniedException("无权限");
}
// ✅ 推荐:只读操作,不修改参数
log.info("用户 {} 访问方法 {}", userId, methodName);
}
private boolean hasPermission(Long userId) {
// 权限校验逻辑
return true;
}
}
适用场景:
-
权限校验(无权限则抛异常)
-
参数合法性检查
-
方法调用日志记录
-
Read-Only 操作
⚠️ 陷阱警告:
@Before("permissionCheck()")
public void badExample(JoinPoint jp) {
Object[] args = jp.getArgs();
((UserDTO) args[0]).setName("modified"); // ⚠️ 虽生效但不推荐!
args[1] = newDefaultValue; // ❌ 无效!Controller 接收不到
}
2.4 @After - 最终通知
作用 :在目标方法执行后(无论成功或异常)运行,类似 finally 块。
特点:
-
总会执行:目标方法正常返回或抛异常都会执行
-
无法获取返回值:不知道方法执行结果
-
无法阻止异常抛出:只是 finally 逻辑
示例:
@Slf4j
@Aspect
@Component
public class ResourceCleanAspect {
@Pointcut("execution(* com.example.service.FileService.process*(..))")
public void fileOperations() {}
@After("fileOperations()")
public void cleanUpResources() {
// 释放文件句柄、关闭流等操作
FileTempHolder.clear();
log.info("资源清理完成");
}
}
适用场景:
-
资源释放(文件句柄、数据库连接)
-
ThreadLocal 清理
-
性能监控结束标记
2.5 @AfterReturning - 返回后通知
作用 :在目标方法成功返回后执行,可获取返回值。
特点:
-
仅正常返回时执行:方法抛异常时不执行
-
可获取返回值 :通过
returning属性绑定 -
无法修改返回值:只能读取
示例:
@Slf4j
@Aspect
@Component
public class ResultCacheAspect {
@Pointcut("execution(* com.example.service.UserService.getUser(..))")
public void userQuery() {}
@AfterReturning(pointcut = "userQuery()", returning = "result")
public void cacheResult(Object result) {
// 1. result 参数就是目标方法的返回值
if (result != null) {
// 2. 写入缓存
cacheManager.put("userCache", result);
log.info("查询结果已写入缓存: {}", result);
}
}
}
适用场景:
-
结果缓存
-
成功日志记录
-
返回值审计
-
数据同步
2.6 @AfterThrowing - 异常后通知
作用 :在目标方法抛出异常后执行,可获取异常对象。
特点:
-
仅异常时执行:方法正常返回时不执行
-
可获取异常 :通过
throwing属性绑定 -
无法吞掉异常:异常会继续向上抛出
示例:
@Slf4j
@Aspect
@Component
public class ExceptionAlertAspect {
@Pointcut("execution(* com.example.service.OrderService.createOrder(..))")
public void orderCreation() {}
@AfterThrowing(pointcut = "orderCreation()", throwing = "ex")
public void sendAlert(Exception ex) {
// 1. ex 就是抛出的异常
String errorMessage = ex.getMessage();
// 2. 发送告警
alertService.send("订单创建失败: " + errorMessage);
// 3. 记录错误日志
log.error("订单创建异常", ex);
}
}
适用场景:
-
异常告警(钉钉、邮件)
-
事务回滚标记
-
错误日志记录
-
失败指标统计
2.7 @Around - 环绕通知(全能型)
作用:包裹整个调用链,可完全控制目标方法的执行。
特点:
-
全能:可改参、可阻止、可改返回值、可捕获异常
-
**必须调用
proceed()**:否则目标方法不执行 -
**必须返回结果 **:返回值会替换原方法返回值
** 示例 1:性能监控 **:
@Slf4j
@Aspect
@Component
public class PerformanceAspect {
@Pointcut("@annotation(com.example.annotation.Monitor)")
public void monitoredMethods() {}
@Around("monitoredMethods()")
public Object monitorTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 方法执行前
long start = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
try {
// 2. 执行目标方法
Object result = joinPoint.proceed();
// 3. 返回后记录性能
long cost = System.currentTimeMillis() - start;
log.info("方法 {} 执行耗时: {}ms", methodName, cost);
return result;
} catch (Exception e) {
// 4. 异常时记录
long cost = System.currentTimeMillis() - start;
log.error("方法 {} 执行异常,耗时: {}ms", methodName, cost, e);
throw e;
}
}
}
** 示例 2:参数修改 **(核心场景):
@Slf4j
@Aspect
@Component
public class ParameterEnhanceAspect {
@Pointcut("execution(* com.example.controller.*.query*(..))")
public void queryMethods() {}
@Around("queryMethods()")
public Object enhanceParameter(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 获取并拷贝参数(防御性编程)
Object[] originalArgs = joinPoint.getArgs();
Object[] processedArgs = Arrays.copyOf(originalArgs, originalArgs.length);
// 2. 遍历并增强参数
for (int i = 0; i < processedArgs.length; i++) {
Object arg = processedArgs[i];
if (arg instanceof BasePermissionQuery) {
// ✅ 正确:创建拷贝后修改
BasePermissionQuery query = (BasePermissionQuery) arg;
query = query.clone(); // 或 new BasePermissionQuery(arg)
query.setUserId(getCurrentUserId());
query.setDataScope(getUserDataScope());
processedArgs[i] = query; // ✅ 显式替换
}
// ✅ 支持替换整个参数
if (arg == null) {
processedArgs[i] = createDefaultQuery();
}
}
log.info("参数增强前: {}, 增强后: {}",
Arrays.toString(originalArgs),
Arrays.toString(processedArgs));
// 3. 传递增强后的参数执行
return joinPoint.proceed(processedArgs);
}
}
** 示例3:阻止方法执行 **:
@Around("execution(* com.example.service.LegacyService.oldMethod(..))")
public Object blockLegacyMethod(ProceedingJoinPoint joinPoint) {
log.warn("阻止调用废弃方法: {}", joinPoint.getSignature());
// ❌ 不调用 proceed(),直接返回降级结果
return "该功能已下线,请联系管理员";
}
** 适用场景 **:
-
** 参数修改 **:唯一可靠方式
-
** 性能监控 **:计时、统计
-
** 事务管理 **:
@Transactional的底层实现 -
** 缓存 **:有则返回,无则查询并缓存
-
** 权限控制 **:阻止无权限调用
2.8 @Order - 切面执行顺序
** 作用 **:当多个切面作用于同一方法时,控制执行顺序。
** 规则 **:
-
** 值越小越先执行 **(越靠近目标方法)
-
默认值
Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE) -
负值可让切面优先执行
** 示例 **:
// 切面1:安全校验(最高优先级)
@Aspect
@Component
@Order(1) // 最先执行
public class SecurityAspect {
@Before("execution(* com.example..*Service.*(..))")
public void check() { /* ... */ }
}
// 切面2:日志记录(次之)
@Aspect
@Component
@Order(2)
public class LoggingAspect {
@Around("execution(* com.example..*Service.*(..))")
public Object log(ProceedingJoinPoint pjp) throws Throwable { /* ... */ }
}
// 切面3:性能监控(最后)
@Aspect
@Component
@Order(3)
public class PerformanceAspect {
@Around("execution(* com.example..*Service.*(..))")
public Object monitor(ProceedingJoinPoint pjp) throws Throwable { /* ... */ }
}
执行顺序可视化:
请求进入
↓
@Order(3) PerformanceAspect(外层)→ start
↓ [proceed()]
@Order(2) LoggingAspect(中层)→ start
↓ [proceed()]
@Order(1) SecurityAspect(内层)→ check
↓
目标方法执行
↓
@Order(1) SecurityAspect
↓
@Order(2) LoggingAspect → end
↓
@Order(3) PerformanceAspect → end
要点:
-
相同
@Order值:按切面类名排序(不确定,避免依赖) -
不同类型通知 :
@Around>@Before>@After>@AfterReturning/@AfterThrowing -
推荐 :每个切面明确指定
@Order,避免歧义
3. 完整实战示例
3.1 项目结构
src/main/java/com/example/demo/
├── DemoApplication.java
├── annotation/
│ └── LogExecution.java // 自定义注解
├── aspect/
│ ├── LoggingAspect.java // 日志切面
│ ├── SecurityAspect.java // 安全切面
│ └── PerformanceAspect.java // 性能切面
├── controller/
│ └── UserController.java // 测试控制器
└── service/
└── UserService.java // 测试服务
3.2 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
String value() default "";
}
3.3 多切面完整代码
UserService.java:
@Service
public class UserService {
@LogExecution("查询用户")
public User getUser(Long id, String name) {
System.out.println("【目标方法】查询用户: id=" + id + ", name=" + name);
return new User(id, name);
}
}
LoggingAspect.java(@Before + @AfterReturning):
@Slf4j
@Aspect
@Component
@Order(2)
public class LoggingAspect {
@Pointcut("@annotation(com.example.demo.annotation.LogExecution)")
public void loggingPointCut() {}
@Before("loggingPointCut()")
public void logBefore(JoinPoint joinPoint) {
String method = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("【日志-前置】方法: {}, 参数: {}", method, Arrays.toString(args));
}
@AfterReturning(pointcut = "loggingPointCut()", returning = "result")
public void logAfter(Object result) {
log.info("【日志-返回】结果: {}", result);
}
}
SecurityAspect.java(@Around):
@Slf4j
@Aspect
@Component
@Order(1)
public class SecurityAspect {
@Around("@annotation(logExecution)")
public Object checkSecurity(ProceedingJoinPoint joinPoint, LogExecution logExecution) throws Throwable {
String method = joinPoint.getSignature().getName();
log.info("【安全-环绕】开始检查方法: {}, 注解: {}", method, logExecution.value());
// 参数校验
Object[] args = joinPoint.getArgs();
Long userId = (Long) args[0];
if (userId <= 0) {
throw new IllegalArgumentException("非法用户ID: " + userId);
}
// 参数增强
args[1] = "安全增强_" + args[1];
try {
Object result = joinPoint.proceed(args);
log.info("【安全-环绕】方法 {} 执行成功", method);
return result;
} catch (Exception e) {
log.error("【安全-环绕】方法 {} 执行异常", method, e);
throw e;
}
}
}
PerformanceAspect.java(@Around):
@Slf4j
@Aspect
@Component
@Order(3)
public class PerformanceAspect {
@Around("@annotation(com.example.demo.annotation.LogExecution)")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
String method = joinPoint.getSignature().getName();
log.info("【性能-环绕】方法 {} 开始执行", method);
Object result = joinPoint.proceed();
long cost = System.currentTimeMillis() - start;
log.info("【性能-环绕】方法 {} 执行耗时: {}ms", method, cost);
return result;
}
}
UserController.java:
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id, @RequestParam String name) {
return userService.getUser(id, name);
}
}
3.4 测试与输出
访问:GET /user/123?name=zhangsan
控制台输出:
【性能-环绕】方法 getUser 开始执行
【安全-环绕】开始检查方法: getUser, 注解: 查询用户
【日志-前置】方法: getUser, 参数: [123, zhangsan]
【目标方法】查询用户: id=123, name=安全增强_zhangsan
【日志-返回】结果: User(id=123, name=安全增强_zhangsan)
【安全-环绕】方法 getUser 执行成功
【性能-环绕】方法 getUser 执行耗时: 15ms
3.5 异常测试
访问:GET /user/-1?name=invalid
控制台输出:
【性能-环绕】方法 getUser 开始执行
【安全-环绕】开始检查方法: getUser, 注解: 查询用户
【安全-环绕】方法 getUser 执行异常 // 异常被安全切面捕获
java.lang.IllegalArgumentException: 非法用户ID: -1
【性能-环绕】方法 getUser 执行耗时: 3ms // 性能切面记录到异常
4. 最佳实践与常见陷阱
4.1 核心原则
-
职责单一:一个切面只负责一个横切关注点(日志、安全、性能分离)
-
明确顺序 :总是使用
@Order指定执行顺序 -
参数只读 :
@Before中绝不修改参数(即使对象内部状态) -
环绕全能 :需要修改参数、控制流程时,一律使用
@Around -
防御拷贝 :
@Around中修改参数前先拷贝,避免副作用 -
异常处理 :
@Around必须捕获异常并再次抛出,否则吞掉异常
4.2 常见陷阱
| 陷阱代码 | 问题 | 正确做法 |
|---|---|---|
@Before 中 args[i] = newObj |
无效,Controller 收不到 | 改用 @Around + proceed(newArgs) |
@Before 中修改对象属性 |
虽生效但隐藏副作用 | 改用 @Around,明确克隆后修改 |
@AfterReturning 中抛异常 |
异常会覆盖原方法返回值 | 仅用于日志/缓存,不抛业务异常 |
@Around 忘记 proceed() |
目标方法不执行 | 确保有返回 proceed() 结果 |
@Around 吞掉异常 |
调用方收不到异常 | catch 后必须 throw e |
多个切面不指定 @Order |
执行顺序不确定 | 所有切面都指定 @Order |
4.3 性能建议
-
@Around有一定性能开销,简单场景优先用@Before -
切点表达式要精确,避免扫描过多方法
-
避免在切面中执行耗时操作(如远程调用)
-
生产环境可动态开关切面(通过配置中心)
5. 总结决策图
是否需要阻止方法执行?
├─ 是 → @Around
└─ 否 → 是否需要修改参数?
├─ 是 → @Around
└─ 否 → 是否只需方法前执行?
├─ 是 → @Before
└─ 否 → 是否只需方法后执行?
├─ 是 → @AfterReturning / @After
└─ 否 → 重新设计切面职责
一句话总结 :@Before、@After 是观察者 ,@Around 是代理人。能观察不代理,需代理必用环绕