Spring AOP中通知 (Advice) 是什么?

在这里插入图片描述

什么是通知 (Advice)?

通知 (Advice) 是切面(Aspect)的具体动作,它封装了你希望在目标方法执行时插入的代码逻辑

简单来说,如果切面(Aspect)回答了“我要对谁(Pointcut)做什么(Advice)”,那么通知(Advice)就是那个具体的“做什么”。


通知定义了“在何时 (When)”和“做什么 (What)”

通知(Advice)的核心职责就是定义两件事:

1. 在何时 (When) - 执行时机

这指的是代码逻辑应该在目标方法执行过程中的哪个时间点被触发。Spring AOP 提供了五种标准的执行时机,通过不同的注解来表示:

通知类型 (Advice Type)注解 (Annotation)“在何时” (When)
前置通知 (Before Advice)@Before在目标方法执行之前
后置通知 (After Advice)@After在目标方法执行之后(无论成功还是异常,类似于 finally)。
返回通知 (After Returning)@AfterReturning在目标方法成功执行并返回结果之后。
异常通知 (After Throwing)@AfterThrowing在目标方法执行过程中抛出异常之后。
环绕通知 (Around Advice)@Around包裹着整个目标方法的执行过程,可以控制方法执行的前后。

选择正确的“时机”至关重要。例如:

  • 权限检查必须在方法执行 (@Before)。
  • 记录成功操作的日志应该在方法成功返回 (@AfterReturning),因为你需要知道操作确实成功了。
  • 事务管理需要在方法执行开启事务,在方法成功后提交,在异常时回滚,这完美契合环绕通知 (@Around) 的能力。
2. 做什么 (What) - 执行的逻辑

这指的是你希望注入的具体代码,也就是横切关注点(Cross-Cutting Concern)的实现。这部分就是你用 Java 编写的普通方法体。

“做什么”的例子:

  • 日志记录logger.info("方法开始执行,参数是...")
  • 权限验证if (!SecurityUtils.hasPermission(...)) { throw new AccessDeniedException("无权访问"); }
  • 事务管理transactionManager.begin(); ... pjp.proceed(); ... transactionManager.commit();
  • 性能监控long start = System.currentTimeMillis(); ... long end = System.currentTimeMillis(); logger.info("耗时: " + (end - start) + "ms");

五种通知类型的详细解析和示例

让我们通过一个代码示例来更清晰地理解这五种通知。

假设我们有一个切点 serviceLog(),它匹配所有 service 包下的方法。

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAdvice {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    // 定义一个切点,方便复用
    @Pointcut("execution(* com.example.service.UserService.findUserById(..))")
    public void userSearchPointcut() {}

    /**
     * 1. 前置通知 (@Before)
     * 何时: 在 findUserById 方法执行前。
     * 做什么: 打印方法即将开始执行的日志。
     */
    @Before("userSearchPointcut()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        // 这是 "做什么 (What)"
        logger.info("[@Before] 方法 '{}' 即将执行,参数: {}", methodName, args);
    }

    /**
     * 2. 后置通知 (@After)
     * 何时: 在 findUserById 方法执行后 (无论是否抛出异常)。
     * 做什么: 打印方法已执行结束的日志。
     */
    @After("userSearchPointcut()")
    public void logAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        // 这是 "做什么 (What)"
        logger.info("[@After] 方法 '{}' 执行结束。", methodName);
    }

    /**
     * 3. 返回通知 (@AfterReturning)
     * 何时: 在 findUserById 方法成功返回后。
     * 做什么: 打印方法的返回值。
     * returning = "result" 将方法的返回值绑定到名为 result 的参数上。
     */
    @AfterReturning(pointcut = "userSearchPointcut()", returning = "result")
    public void logReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        // 这是 "做什么 (What)"
        logger.info("[@AfterReturning] 方法 '{}' 成功返回,返回值: {}", methodName, result);
    }

    /**
     * 4. 异常通知 (@AfterThrowing)
     * 何时: 在 findUserById 方法抛出异常后。
     * 做什么: 打印异常信息。
     * throwing = "e" 将抛出的异常绑定到名为 e 的参数上。
     */
    @AfterThrowing(pointcut = "userSearchPointcut()", throwing = "e")
    public void logThrowing(JoinPoint joinPoint, Exception e) {
        String methodName = joinPoint.getSignature().getName();
        // 这是 "做什么 (What)"
        logger.error("[@AfterThrowing] 方法 '{}' 抛出异常: {}", methodName, e.getMessage());
    }

    /**
     * 5. 环绕通知 (@Around) - 最强大的通知
     * 何时: 包裹整个目标方法的执行过程。
     * 做什么: 记录方法执行耗时。
     * 必须接收 ProceedingJoinPoint 作为参数,并手动调用 pjp.proceed() 来执行目标方法。
     */
    @Around("execution(* com.example.service.UserService.updateUser(..))") // 换个方法演示
    public Object profile(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        logger.info("[@Around] 进入环绕通知...");

        // 手动执行目标方法
        Object result = pjp.proceed(); // 这是调用目标方法的分界点

        long end = System.currentTimeMillis();
        logger.info("[@Around] 目标方法执行完毕,耗时: {} 毫秒", (end - start));

        // 可以修改返回值
        // return "some modified result";
        
        return result;
    }
}

总结

通知类型定义了“何时”定义了“做什么”关键特征
@Before方法执行前前置处理逻辑(如参数校验)无法阻止方法执行,无法获取返回值
@After方法执行后资源清理等 finally 型逻辑无论成功或异常都会执行
@AfterReturning方法成功返回后针对成功结果的后处理可以获取到方法的返回值
@AfterThrowing方法抛出异常后异常记录和处理可以获取到抛出的异常
@Around包裹方法执行事务、缓存、性能监控等复杂逻辑功能最强,可以决定是否执行目标方法、修改参数和返回值

因此,通知(Advice) 正是 AOP 中承载“横切”功能的代码实体。通过选择不同类型的通知,开发者可以精确地控制附加逻辑的执行时机和行为,从而实现优雅、无侵入的功能增强。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰糖心书房

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

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

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

打赏作者

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

抵扣说明:

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

余额充值