一、演示几种通知类型
我们先写两个测试接口:
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";
}
}
我们先有一个切面:

加两个注解

- @Around:环绕通知,此注解标注的通知方法在目标方法前后都被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常,都会执行
- @AfterReturning:返回后通知,此注解标志的通知方法在目标方法后被执行,有异常不会执行
- @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 没有执行,@Around 中 proceed() 之后的代码也没有执行。
执行顺序图解:
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 的特殊之处
- 必须调用
joinPoint.proceed(),否则目标方法不会执行。 - 返回值必须是
Object,用来接收目标方法的返回值并返回给调用者。 - 可以控制目标方法是否执行 :不调用
proceed(),目标方法就被"短路"了。 - 可以修改返回值 :拿到
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 按照切面类的类名字母顺序排序。
比如 AspectA 和 AspectB,AspectA 的 @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 两种方式,以及如何用它们精确匹配你想要增强的方法。敬请期待~🚀