@Around 是 Spring AOP 最灵活的通知类型,通过 ProceedingJoinPoint 可控制目标方法执行、修改参数、包装返回值、捕获异常;必须且仅能调用一次 proceed(),需注意线程安全与性能开销。

Spring AOP 的 @Around 是最灵活的通知类型,它能完全接管目标方法的执行流程:你可以决定是否执行原方法、修改传入参数、拦截并包装返回值,甚至抛出异常替代原逻辑。
理解 ProceedingJoinPoint 的核心作用
@Around 方法的第一个参数必须是 ProceedingJoinPoint(或其子类 JoinPoint 的扩展),它封装了目标方法的全部上下文。关键能力包括:
-
获取参数:
pjp.getArgs()返回 Object[],可直接修改数组元素(注意:仅对引用类型生效,基本类型包装类修改后需重新 setArgs) -
执行原方法:
pjp.proceed()触发目标方法调用;pjp.proceed(Object[] newArgs)可传入新参数数组 -
获取返回值:
proceed()的返回值即目标方法结果,可任意转换、包装或替换 -
捕获异常:用 try-catch 包裹
proceed(),可吞掉异常、转译异常或记录后继续抛出
手动控制方法执行:条件跳过或提前返回
不调用 pjp.proceed() 就不会执行目标方法。常见场景如权限校验失败、缓存命中、开关降级:
@Around("execution(* com.example.service.*.*(..))")
public Object checkAndSkip(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
// 示例:第一个参数为 null 时直接返回默认值
if (args.length > 0 && args[0] == null) {
return "fallback-result";
}
// 否则放行
return pjp.proceed();
}修改入参:安全地替换或增强参数
直接修改 getArgs() 返回的数组有效,但要注意:
立即学习“Java免费学习笔记(深入)”;
- 若参数是不可变对象(如 String、Integer),需新建实例赋值
- 修改后必须显式调用
pjp.proceed(newArgs)才会生效 - 避免修改原始参数对象状态,以防副作用影响其他切面或业务逻辑
示例:为所有方法自动追加当前用户 ID(假设 ThreadLocal 中已存):
@Around("execution(* com.example.service..*(..))")
public Object injectUserId(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs().clone(); // 避免污染原数组
Long userId = UserContext.getCurrentUserId();
if (userId != null) {
// 假设约定最后一个参数是 Long userId,动态插入
Object[] newArgs = Arrays.copyOf(args, args.length + 1);
newArgs[newArgs.length - 1] = userId;
return pjp.proceed(newArgs);
}
return pjp.proceed();
}包装返回值:统一响应结构或数据脱敏
在 proceed() 后处理返回值是最常用方式。例如统一封装为 Result<T>:
@Around("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public Object wrapResponse(ProceedingJoinPoint pjp) throws Throwable {
Object result = pjp.proceed();
// 若已是 Result 类型,不再包装
if (result instanceof Result) {
return result;
}
// 否则包装
return Result.success(result);
}也可结合泛型擦除做更精细判断,或对特定类型(如 List、Map)做字段脱敏处理。
注意事项与避坑点
使用 @Around 时需警惕:
- 必须调用 proceed() 一次且仅一次:多次调用会重复执行目标方法;不调用则业务逻辑丢失
- 异常传播要明确:catch 异常后若不 re-throw,上层无法感知错误;建议记录日志后再 throw 或转译
- 不要在环绕通知中修改 JoinPoint 状态以外的共享变量,尤其避免在多线程环境下操作静态变量或单例 Bean 的可变字段
- 性能敏感场景慎用:每次调用都增加一层代理开销,高频方法建议用编译期织入(AspectJ)或重构为模板方法


















