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 方法中展示这一点:

@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 等通知,因为它们意图更清晰,代码更简单,也更不容易出错。
### 使用 `@Around` 注解排除特定方法或条件 当使用 Spring AOP 中的 `@Around` 注解时,可以通过多种方式来实现对特定方法或条件下不应用通知逻辑的需求。一种常见的方式是在编写切入点表达式时利用 SpEL(Spring Expression Language) 来过滤掉不需要增强处理的目标对象及其操作。 #### 方法一:通过切入点表达式的否定运算符 可以在定义切入点的时候采用 `!execution()` 结合其他匹配模式来构建复杂的路径规则,从而达到只针对部分符合条件的方法生效的目的。例如: ```java @Pointcut("!execution(* com.example.service..*.*(..)) && execution(public * *(..))") private void nonServicePublicMethods() {} ``` 上述代码表示除了服务层(`com.example.service`)下的所有公共成员函数外都适用此切入点[^1]。 对于更细粒度控制到单个方法级别,则可进一步精确化表达式内容;如果要基于参数特性做判断的话还可以引入额外变量参与计算。 #### 方法二:借助自定义注解配合元数据查询 创建一个新的标注用于标记那些希望绕过环绕建议的应用场景,并将其纳入考虑范围内作为筛选依据之一。之后在实际编码过程中只需简单地给对应位置加上该标签即可轻松实现例外情况管理。 先声明一个简单的无属性版本: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SkipAround { } ``` 接着调整原有 Aspect 类内部逻辑使之能够识别并响应此类特殊指示信号: ```java @Aspect @Component public class MyAspect { @Around("@annotation(skip)") public Object skipSpecificMethod(ProceedingJoinPoint joinPoint, SkipAround skip) throws Throwable { System.out.println("Skipping around advice for method marked with @SkipAround"); return joinPoint.proceed(); } @Around("execution(* com.example..*(..)) && !@annotation(SkipAround)") public Object applyAdviceExceptSkippedMethods(ProceedingJoinPoint pjp) throws Throwable { // 增强逻辑... try { System.out.println("Before the target method executes."); Object result = pjp.proceed(); // 执行连接点处原生行为 System.out.println("After returning from the target method."); return result; } catch (Throwable ex) { System.err.println("An exception was thrown by the target method."); throw ex; } } } ``` 这里展示了两个不同的环绕通知——前者专门服务于带有 `@SkipAround` 装饰者特征的对象实例;后者则相反,它会作用于除去了前述特例之外的一切候选集合之上[^4]。 #### 方法三:运用内置工具类简化配置工作量 考虑到灵活性以及维护成本等因素的影响,在某些情况下也可以尝试依靠框架自带的支持机制来进行更为优雅的操作。比如借助 `org.springframework.core.annotation.AnnotationUtils.findAnnotation(Class<T>, Class<A>)` 函数快速定位是否存在某项兴趣点上的附加信息描述,进而决定是否继续后续流程。 综上所述,以上三种途径均能有效地帮助开发者达成预期效果即有条件的选择性忽略不必要的干扰因素而专注于核心业务本身的发展需求。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值