Spring Aop @After (后置通知)的使用场景?

在这里插入图片描述

核心定义

@After 是 Spring AOP 中的另一种通知(Advice)类型,通常被称为“后置通知”“最终通知”

它的核心作用是:

无论目标方法是正常执行完成,还是在执行过程中抛出了异常,@After 通知中的代码 总是 会在目标方法执行之后被执行。

最经典的类比就是 Java 中的 try...catch...finally 语句块里的 finally 部分。@After 的行为和 finally 块的行为几乎一模一样。


@After 通知的执行流程

为了更好地理解,我们来看两种情况下的执行顺序:

情况一:目标方法成功执行

  1. @Before 通知执行。
  2. 目标方法 (targetMethod()) 执行并正常返回。
  3. @After 通知执行。
  4. (如果定义了)@AfterReturning 通知执行。

情况二:目标方法抛出异常

  1. @Before 通知执行。
  2. 目标方法 (targetMethod()) 执行,中途抛出异常。
  3. @After 通知执行。
  4. (如果定义了)@AfterThrowing 通知执行。
  5. 异常继续向上层调用栈抛出。

一个非常关键的点是:@After 通知本身无法访问目标方法的返回值(因为它可能根本没有返回值,比如抛异常时),也无法捕获或处理从目标方法中抛出的异常。它只是一个保证“最后一定会被执行”的钩子。


@After 通知能做什么?(主要应用场景)

后置通知非常适合执行那些必须进行的“清理”或“收尾”工作,无论业务逻辑成功与否。

  1. 资源释放 (Resource Cleanup)

    • 这是 @After 最重要、最常见的用途。类似于 finally 块。
    • 示例:释放文件句柄、关闭网络连接、关闭数据库连接池中的连接等。确保即使业务代码出错,关键资源也不会被泄露。
  2. 上下文清理 (Context Cleanup)

    • 如果在 @Before 通知中向 ThreadLocal 存放了数据,那么在 @After 通知中将其 remove() 是一个最佳实践。这可以防止在线程池环境中发生内存泄漏或数据错乱。
  3. 最终日志记录 (Final Auditing)

    • 记录一个操作的结束。
    • 示例:“方法 updateProduct 执行完毕。” 这个日志不关心成功或失败,只记录“结束”这个事实。
  4. 性能监控 (Performance Monitoring)

    • 可以在 @Before 中记录一个开始时间,然后在 @After 中记录结束时间,并计算总耗时。
    • 示例
      • @Before: long startTime = System.currentTimeMillis(); (存入 ThreadLocal)
      • @After: long endTime = System.currentTimeMillis(); long duration = endTime - startTime; log.info("方法耗时: {} ms", duration);

@AfterReturning@AfterThrowing 的区别

这是新手很容易混淆的地方,理解它们的区别至关重要:

通知类型执行时机能否访问返回值?能否访问异常?主要用途
@After (最终通知)总是在目标方法后执行(无论成功或失败)不能不能资源清理、最终日志
@AfterReturning (返回通知)仅在目标方法成功执行后执行可以不适用基于返回结果的附加操作
@AfterThrowing (异常通知)仅在目标方法抛出异常后执行不适用可以异常日志记录、告警通知

简单来说:

  • 总是执行清理?用 @After
  • 想在成功后根据返回值做点事?用 @AfterReturning
  • 想在失败后专门处理异常?用 @AfterThrowing

代码示例

我们扩展之前的例子,增加一个删除方法(可能会失败),并为所有方法添加 @After 通知。

1. 业务服务类 (目标对象)

package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    // 成功执行的例子
    public String findUserById(Long id) {
        System.out.println("--- 核心业务逻辑:正在根据 ID 查询用户... ---");
        return "User" + id;
    }

    // 抛出异常的例子
    public void deleteUser(Long id) {
        System.out.println("--- 核心业务逻辑:正在尝试删除用户... ---");
        if (id <= 0) {
            throw new IllegalArgumentException("用户ID无效,删除失败!");
        }
        System.out.println("用户 " + id + " 已被成功删除。");
    }
}

2. 切面类 (Aspect) 中定义 @After 通知

package com.example.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;

@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(public * com.example.service.*.*(..))")
    public void serviceLayerPointcut() {}

    // 前置通知
    @Before("serviceLayerPointcut()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("==================================================");
        System.out.printf("[AOP 前置通知]: 方法 [%s] 即将执行... 参数: %s%n", methodName, Arrays.toString(args));
    }

    // 后置通知 (最终通知)
    @After("serviceLayerPointcut()")
    public void logAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.printf("[AOP 后置通知]: 方法 [%s] 执行完毕。执行清理工作...%n", methodName);
        System.out.println("--------------------------------------------------\n");
    }
}

3. 运行代码并观察输出

调用成功的方法 userService.findUserById(101L):

==================================================
[AOP 前置通知]: 方法 [findUserById] 即将执行... 参数: [101]
--- 核心业务逻辑:正在根据 ID 查询用户... ---
[AOP 后置通知]: 方法 [findUserById] 执行完毕。执行清理工作...
--------------------------------------------------

@After 在方法成功后执行了。

调用失败的方法 userService.deleteUser(0L) (需要用 try-catch 捕获异常):

try {
    userService.deleteUser(0L);
} catch (Exception e) {
    System.err.println("在调用方捕获到异常: " + e.getMessage());
}

输出:

==================================================
[AOP 前置通知]: 方法 [deleteUser] 即将执行... 参数: [0]
--- 核心业务逻辑:正在尝试删除用户... ---
[AOP 后置通知]: 方法 [deleteUser] 执行完毕。执行清理工作...
--------------------------------------------------

在调用方捕获到异常: 用户ID无效,删除失败!

即使 deleteUser 抛出了异常,@After 通知 (logAfter 方法) 依然被执行了,完美地展示了其 finally 的特性。

总结

特性描述
执行时机无论成功或失败,总是在目标方法执行之后执行。
核心用途资源释放上下文清理、最终日志记录等收尾工作。
行为类似Java 的 finally 语句块。
关键限制无法访问目标方法的返回值,也无法捕获或修改异常。
关键参数可以注入 JoinPoint 对象,获取方法元数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰糖心书房

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

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

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

打赏作者

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

抵扣说明:

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

余额充值