Spring Aop @Around (环绕通知)的使用场景

核心定义

@Around 通知将目标方法"包裹"起来,像一个三明治一样,目标方法是中间的馅料,而 @Around 通知是上下的两片面包。

它的核心作用是:

在目标方法调用之前和之后,都可以执行自定义的逻辑,并且它拥有完全控制权,可以决定目标方法是否被执行、如何被执行,甚至可以修改参数和返回值。

它就像一个拥有最高权限的"代理守卫",不仅能在目标方法执行前后进行检查和记录,还能决定是否放行、篡改通行证(参数),甚至伪造一个结果(返回值)直接返回,让调用者以为目标方法已经执行了。


@Around 通知能做什么?(它的强大之处)

@Around 几乎可以做任何你能在切面中想到的事情,因为它控制了整个调用链。

  1. 控制目标方法的执行

    • 可以决定是否执行 :通过选择是否调用 proceedingJoinPoint.proceed(),你可以完全阻止目标方法的执行。
    • 可以重复执行 :你可以在一个循环或重试逻辑中多次调用 proceed()
  2. 修改目标方法的参数

    • 在调用 proceed() 时,可以传入一个新的参数数组 proceed(newArgs[]),从而在不修改原始业务代码的情况下,改变传入目标方法的数据。
  3. 修改目标方法的返回值

    • 可以捕获 proceed() 的返回结果,然后返回一个全新的、被修改过的结果,甚至是一个完全不同类型的结果。
  4. 异常处理与转换

    • 可以在 try-catch 块中调用 proceed()。如果目标方法抛出异常,@Around 通知可以捕获它,然后:
      • 记录异常后,重新抛出。
      • "吞掉"异常,返回一个默认值或 null,让调用者认为操作是成功的。
      • 将一种类型的异常包装成另一种异常再抛出。
  5. 实现所有其他通知的功能

    • @Around 是其他所有通知的"超集"。它一个通知就能实现 @Before, @After, @AfterReturning, @AfterThrowing 的全部功能。
  6. 最常见的应用场景

    • 性能监控 :在 proceed() 调用前后记录时间,计算方法执行耗时,这是最经典的应用。
    • 缓存实现 :在方法执行前(调用 proceed() 前)检查缓存。如果缓存命中,就直接返回缓存数据,根本不执行目标方法。如果未命中,则调用 proceed(),获取结果,存入缓存,再返回。
    • 事务管理 :Spring 的 @Transactional 注解就是通过 @Around 通知实现的。它在 proceed() 之前开启事务,在之后根据执行结果(正常返回或抛出异常)来提交或回滚事务。
    • 重试机制 :当 proceed() 抛出特定异常时,在 catch 块中进行重试,而不是立即失败。

为什么需要手动调用 proceedingJoinPoint.proceed()

这是理解 @Around 的关键所在。

@Around 通知不像其他通知那样是"被动"的。其他通知(如 @Before)只是在特定的时间点被 AOP 框架回调一下,执行完自己的逻辑后,控制权就自动交还给框架了。

@Around 通知是主动控制流程 的。当 AOP 框架调用 @Around 通知时,它把执行的控制权完全交给了你。目标方法此时处于"暂停"状态。

proceedingJoinPoint.proceed() 这个方法调用,就是你作为"控制者"发出的一个指令:好了,我的前置逻辑执行完了,现在请继续执行调用链中的下一个环节(可能是另一个切面,也可能是最终的目标方法)。

  • 如果不调用 proceed()

    • 调用链在此中断,目标方法将永远不会被执行
    • 你的 @Around 通知方法必须自己提供一个返回值(如果目标方法有返回值的话),否则调用者会得到 null
    • 这正是实现缓存和安全拦截等"短路"逻辑的原理。
  • 调用 proceed() 的返回值

    • proceed() 方法的返回值就是目标方法的返回值。你可以捕获它、修改它,或者原封不动地返回。
  • ProceedingJoinPoint vs JoinPoint

    • 注意,@Around 通知的参数是 ProceedingJoinPoint,而其他通知是 JoinPoint
    • ProceedingJoinPointJoinPoint 的子接口,它只增加了一个核心方法,那就是 proceed()。这个设计清晰地表明了只有 @Around 通知才有能力"继续执行"调用链。

@Around 和其它四种通知的关系

@Around 可以完全模拟其他四种通知的行为。让我们在一个 @Around 方法中展示这一点:

java 复制代码
@Aspect
@Component
public class ComprehensiveAspect {

    @Pointcut("execution(* com.example.service.MyService.doWork(..))")
    public void myServicePointcut() {}

    @Around("myServicePointcut()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        
        // 1. 这部分代码等效于 @Before (前置通知)
        System.out.println("[@Around] ==> @Before: 方法 " + pjp.getSignature().getName() + " 执行前...");

        Object returnValue = null;
        try {
            // 2. 调用目标方法。这是整个通知的核心。
            returnValue = pjp.proceed(); // 如果没有这句,目标方法不会执行

            // 3. 这部分代码等效于 @AfterReturning (返回通知)
            // 只有在 pjp.proceed() 成功执行后才会到达这里
            System.out.println("[@Around] ==> @AfterReturning: 方法成功执行,返回值为: " + returnValue);

        } catch (Throwable ex) {
            // 4. 这部分代码等效于 @AfterThrowing (异常通知)
            // 只有在 pjp.proceed() 抛出异常时才会到达这里
            System.err.println("[@Around] ==> @AfterThrowing: 方法执行异常,异常信息: " + ex.getMessage());
            throw ex; // 必须重新抛出,否则异常就被"吞掉"了
        } finally {
            // 5. 这部分代码等效于 @After (后置/最终通知)
            // 无论成功还是失败,都会执行
            System.out.println("[@Around] ==> @After: 方法执行完毕。");
        }

        // 可以修改返回值
        // return "A modified value from Aspect";
        
        return returnValue; // 返回原始的或修改后的值
    }
}

总结

特性 描述
执行时机 完全包裹目标方法,在其执行前后都有机会执行代码。
核心能力 控制 目标方法是否执行、修改 参数、修改 返回值、处理异常。
关键方法 proceedingJoinPoint.proceed(),用于手动触发目标方法的执行。
与其他通知关系 功能上的超集 ,可以用一个 @Around 通知实现其他所有通知的功能。
使用原则 "用牛刀杀鸡"要谨慎 。由于其复杂性和强大的控制力,只在确实需要修改流程、返回值或进行缓存等复杂操作时才使用它。对于简单的日志、权限检查等,优先使用更简单的 @Before@AfterReturning 等通知,因为它们意图更清晰,代码更简单,也更不容易出错。