【JavaEE28-后端部分】Spring AOP 通知详解——五种“增强时机”,一网打尽

一、演示几种通知类型

我们先写两个测试接口:

java 复制代码
package com.zhongge.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName TestController
 * @Description TODO aop测试
 * @Author 笨忠
 * @Date 2026-04-02 16:12
 * @Version 1.0
 */
@RequestMapping("/test")
@RestController
public class TestController {

    @RequestMapping("/t1")
    public Integer t1() {
        return 1;
    }
    @RequestMapping("/t2")
    public Boolean t2() {
        return true;
    }
    @RequestMapping("/t3")
    public String t3() {
        return "t3";
    }
}

java 复制代码
package com.zhongge.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName UserController
 * @Description TODO
 * @Author 笨忠
 * @Date 2026-04-03 14:48
 * @Version 1.0
 */
@RequestMapping("/user")
@RestController
public class UserController {

    @RequestMapping("/u1")
    public String u1() {
        return "u1";
    }

    @RequestMapping("/u2")
    public String u2() {
        return "u2";
    }
}

我们先有一个切面:


加两个注解


  1. @Around:环绕通知,此注解标注的通知方法在目标方法前后都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  3. @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常,都会执行
  4. @AfterReturning:返回后通知,此注解标志的通知方法在目标方法后被执行,有异常不会执行
  5. @AfterThrowing:异常后通知,此注解标注的通知方法,发生异常后执行

代码

java 复制代码
@Aspect
@Component
@Slf4j
public class AspectDemo1 {

    @Around("execution(* com.zhongge.controller.*.*(..))")
    public Object timeRecord(ProceedingJoinPoint pjp) throws Throwable {
        log.info("目标方法执行前面...");
        //执行目标方法
        Object proceed = pjp.proceed();
        log.info("目标方法执行后面...");
        return proceed;
    }

    //前置通知
    @Before("execution(* com.zhongge.controller.*.*(..))")
    public void doBefore() {
        log.info("do Before>>>");
    }

    //后置通知
    @After("execution(* com.zhongge.controller.*.*(..))")
    public void doAfter() {
        log.info("do After>>>");
    }

    //返回后通知
    @AfterReturning("execution(* com.zhongge.controller.*.*(..))")
    public void doAfterReturning() {
        log.info("do AfterReturning>>>");
    }

    //异常后通知
    @AfterThrowing("execution(* com.zhongge.controller.*.*(..))")
    public void doAfterThrowing() {
        log.info("do AfterThrowing>>>");
    }


}

结果

没有异常的时候:


有异常的时候:


注意:连接点只能在around中使用


开篇:你收到过快递通知吗?

老铁们,你有没有发现一个现象:你在淘宝买个东西,快递公司会给你发好多条通知。

  • "您的订单已付款"(下单后立即通知)
  • "您的包裹已出库"(发货前)
  • "您的包裹正在派送中"(派送前)
  • "您的包裹已签收"(签收后)
  • "您的包裹异常,请及时联系"(出问题时)

快递公司在你购物流程的不同阶段,给你发送不同的通知。这就是"不同时机做不同的事"。

在 Spring AOP 中,通知(Advice) 就是这样的"快递通知"------它定义了在目标方法执行的什么阶段,执行什么样的增强代码。

本期,我们就来详细学习 Spring AOP 的五种通知类型,并通过代码和图解,搞清楚它们的执行时机、执行顺序以及注意事项。


二、五种通知类型:一张图看懂


三、五种通知类型详解(生活化)

3.1 @Before 前置通知------进门之前先敲门

概念 :在目标方法执行之前执行。

生活例子:你去朋友家做客,进门之前先敲门。不管朋友在不在家,你都会敲门。

适用场景:日志记录、参数校验、权限检查。

代码示例

java 复制代码
@Before("execution(* com.zhongge.controller.*.*(..))")
public void doBefore() {
    log.info("前置通知:方法即将执行,先打个招呼");
}

3.2 @After 后置通知------出门之后关灯

概念 :在目标方法执行之后 执行,无论方法是否抛出异常,都会执行。

生活例子:你离开朋友家,出门后随手把门关上。即使你在朋友家吵架了,出门后还是要关门。

适用场景:释放资源、清理临时数据、记录最终结果。

代码示例

java 复制代码
@After("execution(* com.zhongge.controller.*.*(..))")
public void doAfter() {
    log.info("后置通知:方法执行完毕,不管有没有异常都来收个尾");
}

3.3 @AfterReturning 返回后通知------正常离开时挥手告别

概念 :在目标方法正常返回 之后执行。如果方法抛出异常,则不会执行

生活例子:你在朋友家玩得很开心,正常离开时,朋友送你到门口挥手告别。如果你和朋友吵架了,就不会有这个告别环节。

适用场景:记录成功日志、缓存更新、返回值处理。

代码示例

java 复制代码
@AfterReturning("execution(* com.zhongge.controller.*.*(..))")
public void doAfterReturning() {
    log.info("返回后通知:方法正常返回,没有异常,可以记录成功日志");
}

3.4 @AfterThrowing 异常后通知------出事了赶紧报警

概念 :在目标方法抛出异常 之后执行。如果方法正常返回,则不会执行

生活例子:你在朋友家不小心打碎了一个花瓶,朋友立刻打电话报警。如果一切正常,就不会有这个环节。

适用场景:记录异常日志、发送告警、事务回滚。

代码示例

java 复制代码
@AfterThrowing("execution(* com.zhongge.controller.*.*(..))")
public void doAfterThrowing() {
    log.info("异常后通知:方法抛出异常,赶紧记录错误日志");
}

3.5 @Around 环绕通知------全程陪同

概念 :在目标方法执行前后都可以执行,是最强大的通知类型。它可以控制目标方法是否执行、修改返回值、处理异常。

生活例子:你请了一个私人导游,他全程陪同你:进门之前先介绍,游玩过程中随时讲解,结束后送你离开。他甚至可以决定"今天不去了,改天再来"。

适用场景:性能监控(统计耗时)、事务管理、权限控制。

代码示例

java 复制代码
@Around("execution(* com.zhongge.controller.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    log.info("环绕通知开始:方法即将执行");
    
    Object result = joinPoint.proceed();  // 执行目标方法
    
    long end = System.currentTimeMillis();
    log.info("环绕通知结束:方法执行耗时 {} ms", end - start);
    return result;
}

四、动手实验:验证五种通知的执行顺序

4.1 创建一个测试切面

java 复制代码
@Slf4j
@Aspect
@Component
public class AllAdviceDemo {

    @Pointcut("execution(* com.zhongge.controller.*.*(..))")
    private void pt() {}

    @Before("pt()")
    public void before() {
        log.info("① @Before 前置通知");
    }

    @After("pt()")
    public void after() {
        log.info("⑤ @After 后置通知(最后执行,无论异常)");
    }

    @AfterReturning("pt()")
    public void afterReturning() {
        log.info("④ @AfterReturning 返回后通知(正常才执行)");
    }

    @AfterThrowing("pt()")
    public void afterThrowing() {
        log.info("④ @AfterThrowing 异常后通知(异常才执行)");
    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("② @Around 环绕通知开始(在 @Before 之前)");
        Object result = joinPoint.proceed();
        log.info("⑥ @Around 环绕通知结束(在 @After 之后)");
        return result;
    }
}

4.2 创建一个测试控制器

java 复制代码
@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/normal")
    public String normal() {
        log.info("★★★ 目标方法正常执行 ★★★");
        return "正常返回";
    }

    @RequestMapping("/error")
    public String error() {
        log.info("★★★ 目标方法即将抛出异常 ★★★");
        int a = 10 / 0;  // 故意制造异常
        return "不会执行到这里";
    }
}

4.3 测试正常情况

访问 http://localhost:8080/test/normal,观察日志:

text 复制代码
② @Around 环绕通知开始(在 @Before 之前)
① @Before 前置通知
★★★ 目标方法正常执行 ★★★
④ @AfterReturning 返回后通知(正常才执行)
⑤ @After 后置通知(最后执行,无论异常)
⑥ @Around 环绕通知结束(在 @After 之后)

执行顺序图解

text 复制代码
Around开始 → Before → 目标方法 → AfterReturning → After → Around结束

4.4 测试异常情况

访问 http://localhost:8080/test/error,观察日志:

text 复制代码
② @Around 环绕通知开始(在 @Before 之前)
① @Before 前置通知
★★★ 目标方法即将抛出异常 ★★★
④ @AfterThrowing 异常后通知(异常才执行)
⑤ @After 后置通知(最后执行,无论异常)

注意@AfterReturning 没有执行,@Aroundproceed() 之后的代码也没有执行。

执行顺序图解

text 复制代码
Around开始 → Before → 目标方法抛出异常 → AfterThrowing → After

五、核心知识点总结

5.1 执行顺序口诀

正常情况

阿彪(Around)先开场,小贝(Before)接着上,业务大哥(目标方法)中间忙,阿瑞(AfterReturning)随后上,阿富(After)最后收场,阿彪(Around)关门谢客。

异常情况

阿彪(Around)先开场,小贝(Before)接着上,业务大哥(目标方法)出状况,阿富(After)照常收场,阿彪后半段不上场。

5.2 各通知的"保命"规则

通知类型 执行时机 异常时是否执行 能否阻止目标方法
@Before 目标方法执行前 会(因为还没执行) 不能
@After 目标方法执行后 不能
@AfterReturning 目标方法正常返回后 不会 不能
@AfterThrowing 目标方法抛出异常后 只会 不能
@Around 包裹整个方法 前半段执行,后半段不执行 能(不调用proceed)

5.3 @Around 的特殊之处

  1. 必须调用 joinPoint.proceed(),否则目标方法不会执行。
  2. 返回值必须是 Object,用来接收目标方法的返回值并返回给调用者。
  3. 可以控制目标方法是否执行 :不调用 proceed(),目标方法就被"短路"了。
  4. 可以修改返回值 :拿到 proceed() 的结果后,可以修改再返回。

六、@Pointcut:把重复的切点表达式抽出来

一个切面类中有多个切面


你有没有发现,之前的每个通知都重复写了 execution(* com.zhongge.controller.*.*(..))?如果切点表达式很长,写很多次就太累了。

Spring 提供了 @Pointcut 注解,可以把切点表达式抽取成一个方法,然后其他地方直接引用。

6.1 抽取切点

java 复制代码
@Pointcut("execution(* com.zhongge.controller.*.*(..))")
private void pt() {}  // 方法名随意,方法体为空

6.2 引用切点

java 复制代码
@Before("pt()")
public void before() { ... }

@After("pt()")
public void after() { ... }

6.3 跨切面类引用

如果其他切面类也想用这个切点,把 private 改成 public

然后通过全限定类名 引用:

java 复制代码
@Before("com.zhongge.aspect.AspectDemo1.pt()")
public void doBefore() { ... }

七、多个切面的执行顺序:@Order

当有多个切面匹配同一个目标方法时,它们的执行顺序是怎样的?默认情况下,Spring 按照切面类的类名字母顺序排序。

比如 AspectAAspectBAspectA@Before 先执行,@After 后执行。

7.1 使用 @Order 手动控制

java 复制代码
@Aspect
@Component
@Order(1)//加载类上
public class AspectA { ... }

@Aspect
@Component
@Order(2)
public class AspectB { ... }

规则

  • @Before 通知:数字越小,越先执行。
  • @After 通知:数字越小,越后执行(因为后置通知是逆向执行)。

7.2 执行顺序图解

text 复制代码
@Before 执行顺序:AspectA(1) → AspectB(2) → 目标方法
@After 执行顺序:AspectB(2) → AspectA(1)

为什么 @After 是反的? 可以理解为"先进后出":先执行的切面,它的 @After 要等到最后才执行,就像叠盘子,先放的盘子最后才能拿走。

最外层优先级最高,最里层优先级最低。


八、结语:记住这三张图就够了

图一:五种通知的执行位置

图二:正常 vs 异常

情况 执行的通知
正常 Around开始 → Before → 目标方法 → AfterReturning → After → Around结束
异常 Around开始 → Before → 目标方法抛异常 → AfterThrowing → After

图三:多切面顺序


下一篇预告

后续内容干货满满,记得点赞👍+关注💖+收藏⭐,不迷路!

下一期,我们将学习切点表达式 的详细语法,包括 execution@annotation 两种方式,以及如何用它们精确匹配你想要增强的方法。敬请期待~🚀

相关推荐
lulu12165440782 小时前
谷歌Gemma 4实战指南:Apache 2.0开源,移动端AI新时代来临
java·开发语言·人工智能·开源·apache·ai编程
程序员阿明2 小时前
spring boot在普通方法中获取HttpServletRequest及其使用的方式
java·spring boot·后端
花千树-0102 小时前
Spring Boot 启动慢排查与优化实战指南
java·spring boot·后端·spring
小江的记录本2 小时前
【Docker】《 Docker 高频常用命令速查表 》
java·前端·后端·http·docker·容器·eureka
kaixiang3002 小时前
若依RuoYi实战
java·服务器·前端
SunnyDays10112 小时前
使用 Java 高效管理 Excel 分页符:添加、删除与预览全攻略
java·excel分页符
一 乐2 小时前
智能农田管理|基于springboot + vue智能农田管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·智能农田管理系统
Mem0rin2 小时前
[Java/数据结构]线性表之栈与队列
java·开发语言·数据结构
东离与糖宝2 小时前
告别Python!Java本地部署Gemma 4:Maven一键集成
java·人工智能