乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@After

定义与作用

@After 声明最终通知(After Advice / Finally Advice),在目标方法执行结束后触发,无论方法是否正常返回还是抛出异常。它类似于 Java 中的 finally 块,适用于资源释放、连接关闭、状态清理等必须执行的操作。

关键特性:

  • @After 一定会执行,不受目标方法执行结果影响
  • @After 无法访问目标方法的返回值,因为方法可能抛出异常而没有返回值
  • @After 无法阻止异常的传播,即使 @After 自身抛出异常,也会覆盖原异常(不推荐在 @After 中抛异常)
  • @After 可以访问 JoinPoint 对象,但无法获取异常对象(获取异常需使用 @AfterThrowing)

适用位置与常用属性

适用位置

@After 标注在切面类的方法上,表示该方法是一个最终通知。该方法必须位于 @Aspect 切面类内部。

@Aspect
@Component
public class ResourceAspect {

    @After("execution(* com.feixiang.service.*.*(..))")
    public void releaseResource(JoinPoint joinPoint) {
        // 最终通知逻辑
    }
}

常用属性

属性类型说明
valueString切入点表达式,或引用 @Pointcut 方法名。唯一必填属性
argNamesString参数名称,用于绑定目标方法参数(通常可省略)

核心原理

@After 在代理链中的执行位置

@After 在目标方法执行结束后触发,无论正常返回还是异常抛出:

@After 与 @AfterReturning / @AfterThrowing 的关系

关键理解:

  • @AfterReturning 只在正常返回时执行,@AfterThrowing 只在异常时执行
  • @After 在两者之后执行,无论正常还是异常都会执行
  • 如果同时存在 @AfterReturning 和 @After,执行顺序是:@AfterReturning → @After
  • 如果同时存在 @AfterThrowing 和 @After,执行顺序是:@AfterThrowing → @After

完整示例:飞翔科技学生管理系统资源释放

场景简述

广州飞翔科技公司的后端开发小崔在学生成绩管理系统中使用了临时文件缓存:查询大量学生成绩时,先将数据写入临时文件,处理完毕后再删除。架构师白歌要求:无论查询成功还是失败,临时文件必须被清理,否则磁盘会被占满。小崔决定使用 @After 实现这一"必须执行"的清理逻辑。

操作前代码/配置

在未使用 @After 之前,小崔在每个 Service 方法中使用 try-finally 手动清理:

@Service
public class StudentScoreService {

    public List<Score> batchQuery(List<Long> studentIds) {
        File tempFile = createTempFile();
        try {
            // 业务逻辑:查询并写入临时文件
            List<Score> scores = scoreDao.findByStudentIds(studentIds);
            writeToTempFile(tempFile, scores);
            return scores;
        } finally {
            // 重复!每个方法都要写 finally
            if (tempFile.exists()) {
                tempFile.delete();
                System.out.println("临时文件已删除:" + tempFile.getName());
            }
        }
    }

    public Score exportScore(Long studentId) {
        File tempFile = createTempFile();
        try {
            // 业务逻辑:导出成绩报表
            Score score = scoreDao.findByStudentId(studentId);
            exportToExcel(tempFile, score);
            return score;
        } finally {
            // 重复!清理逻辑再次出现在业务方法中
            if (tempFile.exists()) {
                tempFile.delete();
                System.out.println("临时文件已删除:" + tempFile.getName());
            }
        }
    }

    private File createTempFile() {
        try {
            return File.createTempFile("score_", ".tmp");
        } catch (IOException e) {
            throw new RuntimeException("创建临时文件失败", e);
        }
    }
}

问题:try-finally 块重复出现在每个业务方法中,业务逻辑被资源清理代码干扰。如果白歌要求增加"关闭数据库连接"的清理逻辑,所有方法都要修改。

使用该注解的完整代码

第一步:定义资源管理切面

@Aspect
@Component
public class ResourceCleanupAspect {

    // 使用 ThreadLocal 存储每个线程的临时文件,避免多线程冲突
    private static final ThreadLocal<File> tempFileHolder = new ThreadLocal<>();

    @Pointcut("execution(* com.feixiang.student.service.*.batchQuery(..)) || " +
              "execution(* com.feixiang.student.service.*.exportScore(..))")
    public void tempFileOperation() {}

    // @Before 创建临时文件
    @Before("tempFileOperation()")
    public void createTempFile(JoinPoint joinPoint) {
        try {
            File tempFile = File.createTempFile("feixiang_score_", ".tmp");
            tempFileHolder.set(tempFile);
            System.out.println("[飞翔科技-资源] 创建临时文件:" + tempFile.getAbsolutePath());
        } catch (IOException e) {
            throw new RuntimeException("创建临时文件失败", e);
        }
    }

    // @After 确保临时文件一定被删除(无论成功或失败)
    @After("tempFileOperation()")
    public void deleteTempFile(JoinPoint joinPoint) {
        File tempFile = tempFileHolder.get();
        if (tempFile != null && tempFile.exists()) {
            boolean deleted = tempFile.delete();
            System.out.println("[飞翔科技-资源] 清理临时文件:" + tempFile.getName() + 
                ",删除结果:" + deleted);
        }
        tempFileHolder.remove();  // 防止内存泄漏
    }
}

第二步:纯净的业务 Service

@Service
public class StudentScoreService {

    private final ScoreDao scoreDao;

    public StudentScoreService(ScoreDao scoreDao) {
        this.scoreDao = scoreDao;
    }

    public List<Score> batchQuery(List<Long> studentIds) {
        // 纯粹的业务逻辑,无任何资源管理代码
        System.out.println("[业务] 批量查询 " + studentIds.size() + " 名学生的成绩");
        List<Score> scores = scoreDao.findByStudentIds(studentIds);
        
        // 模拟处理逻辑(实际可能写入临时文件)
        File tempFile = ResourceCleanupAspect.getCurrentTempFile();
        System.out.println("[业务] 使用临时文件处理数据:" + tempFile.getName());
        
        return scores;
    }

    public Score exportScore(Long studentId) {
        // 纯粹的业务逻辑
        System.out.println("[业务] 导出学生 " + studentId + " 的成绩报表");
        Score score = scoreDao.findByStudentId(studentId);
        
        File tempFile = ResourceCleanupAspect.getCurrentTempFile();
        System.out.println("[业务] 使用临时文件导出报表:" + tempFile.getName());
        
        return score;
    }
}

第三步:为 Service 提供获取当前临时文件的方法

@Aspect
@Component
public class ResourceCleanupAspect {

    private static final ThreadLocal<File> tempFileHolder = new ThreadLocal<>();

    public static File getCurrentTempFile() {
        return tempFileHolder.get();
    }

    // ... 前面的 @Before 和 @After 方法
}

第四步:配置类

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.feixiang.student")
public class AppConfig {
}

操作后运行结果及分析

测试代码:

public class AfterDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = 
            new AnnotationConfigApplicationContext(AppConfig.class);
        
        StudentScoreService service = ctx.getBean(StudentScoreService.class);
        
        // 测试正常执行
        System.out.println("=== 测试正常批量查询 ===");
        service.batchQuery(Arrays.asList(2024001L, 2024002L));
        
        System.out.println("\n=== 测试异常场景 ===");
        try {
            // 模拟一个会抛出异常的操作
            service.exportScore(null);
        } catch (Exception e) {
            System.err.println("捕获异常:" + e.getMessage());
        }
        
        ctx.close();
    }
}

控制台输出:

=== 测试正常批量查询 ===
[飞翔科技-资源] 创建临时文件:C:\Users\AOXIANG\AppData\Local\Temp\feixiang_score_1234567890.tmp
[业务] 批量查询 2 名学生的成绩
[业务] 使用临时文件处理数据:feixiang_score_1234567890.tmp
[飞翔科技-资源] 清理临时文件:feixiang_score_1234567890.tmp,删除结果:true

=== 测试异常场景 ===
[飞翔科技-资源] 创建临时文件:C:\Users\AOXIANG\AppData\Local\Temp\feixiang_score_9876543210.tmp
[业务] 导出学生 null 的成绩报表
捕获异常:学号不能为 null
[飞翔科技-资源] 清理临时文件:feixiang_score_9876543210.tmp,删除结果:true

分析:

  • @After 在两种场景下都执行了临时文件清理:正常返回时和异常抛出时
  • 使用 ThreadLocal 确保每个线程有独立的临时文件,避免多线程环境下的资源竞争
  • @After 中调用 tempFileHolder.remove() 防止 ThreadLocal 内存泄漏
  • 业务代码完全纯净,资源管理逻辑统一由切面维护

易错场景与面试考点

易错场景一:@After 中抛出异常覆盖原异常

反例:

@After("serviceLayer()")
public void cleanup(JoinPoint joinPoint) {
    try {
        releaseResource();
    } catch (Exception e) {
        // 错误!这会覆盖目标方法抛出的原始异常
        throw new RuntimeException("资源释放失败", e);
    }
}

现象:如果目标方法抛出了 BusinessException,而 @After 中又抛出了 RuntimeException,客户端最终看到的是 RuntimeException,原始的业务异常信息丢失。

正确做法:@After 中应捕获所有异常,避免覆盖原异常:

@After("serviceLayer()")
public void cleanup(JoinPoint joinPoint) {
    try {
        releaseResource();
    } catch (Exception e) {
        // 记录日志但不抛出,避免覆盖原异常
        System.err.println("[飞翔科技-警告] 资源释放失败:" + e.getMessage());
    }
}

易错场景二:@After 中试图访问返回值

反例:

@After("serviceLayer()")
public void logAfter(JoinPoint joinPoint) {
    // 错误!@After 无法获取返回值
    Object result = joinPoint.getArgs();  // 这只能获取参数,不是返回值
    System.out.println("返回值:" + result);
}

正确做法:若需访问返回值,使用 @AfterReturning:

@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(Object result) {
    System.out.println("返回值:" + result);
}

面试高频考点

考点一:@After 和 @AfterReturning 的区别?

@After 无论目标方法正常返回还是抛出异常都会执行,类似于 finally 块,且无法访问返回值和异常对象。@AfterReturning 只在目标方法正常返回时执行,可以通过 returning 属性绑定返回值。@AfterThrowing 只在目标方法抛出异常时执行,可以通过 throwing 属性绑定异常对象。

考点二:@After 中抛出异常会怎样?

如果目标方法正常返回,@After 中抛出的异常会返回给客户端,替代正常返回值。如果目标方法已经抛出异常,@After 中抛出的异常会覆盖原始异常,导致客户端看到的是 @After 中的异常,原始异常信息丢失。因此强烈建议 @After 中捕获所有异常,仅做资源清理,不抛出新的异常。

考点三:为什么 @After 类似于 finally 但不完全等同?

两者语义相似:无论正常还是异常都会执行。但存在差异:finally 块在方法返回前执行,可以修改返回值(通过 return 语句);@After 在目标方法已经执行完毕后执行,无法修改返回值。此外,finally 是 Java 语言级别的保证,@After 是 Spring AOP 代理层面的保证,如果代理创建失败或 AOP 未生效,@After 不会执行。

上一页
@Before
下一页
@AfterReturning