
核心定义
@Around
通知将目标方法"包裹"起来,像一个三明治一样,目标方法是中间的馅料,而 @Around
通知是上下的两片面包。
它的核心作用是:
在目标方法调用之前和之后,都可以执行自定义的逻辑,并且它拥有完全控制权,可以决定目标方法是否被执行、如何被执行,甚至可以修改参数和返回值。
它就像一个拥有最高权限的"代理守卫",不仅能在目标方法执行前后进行检查和记录,还能决定是否放行、篡改通行证(参数),甚至伪造一个结果(返回值)直接返回,让调用者以为目标方法已经执行了。
@Around
通知能做什么?(它的强大之处)
@Around
几乎可以做任何你能在切面中想到的事情,因为它控制了整个调用链。
-
控制目标方法的执行
- 可以决定是否执行 :通过选择是否调用
proceedingJoinPoint.proceed()
,你可以完全阻止目标方法的执行。 - 可以重复执行 :你可以在一个循环或重试逻辑中多次调用
proceed()
。
- 可以决定是否执行 :通过选择是否调用
-
修改目标方法的参数
- 在调用
proceed()
时,可以传入一个新的参数数组proceed(newArgs[])
,从而在不修改原始业务代码的情况下,改变传入目标方法的数据。
- 在调用
-
修改目标方法的返回值
- 可以捕获
proceed()
的返回结果,然后返回一个全新的、被修改过的结果,甚至是一个完全不同类型的结果。
- 可以捕获
-
异常处理与转换
- 可以在
try-catch
块中调用proceed()
。如果目标方法抛出异常,@Around
通知可以捕获它,然后:- 记录异常后,重新抛出。
- "吞掉"异常,返回一个默认值或
null
,让调用者认为操作是成功的。 - 将一种类型的异常包装成另一种异常再抛出。
- 可以在
-
实现所有其他通知的功能
@Around
是其他所有通知的"超集"。它一个通知就能实现@Before
,@After
,@AfterReturning
,@AfterThrowing
的全部功能。
-
最常见的应用场景
- 性能监控 :在
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
vsJoinPoint
:- 注意,
@Around
通知的参数是ProceedingJoinPoint
,而其他通知是JoinPoint
。 ProceedingJoinPoint
是JoinPoint
的子接口,它只增加了一个核心方法,那就是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 等通知,因为它们意图更清晰,代码更简单,也更不容易出错。 |