首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Spring AOP】通知类型,@Pointcut、@Order(切面优先级)

【Spring AOP】通知类型,@Pointcut、@Order(切面优先级)

作者头像
椰椰椰耶
发布2025-07-21 09:05:00
发布2025-07-21 09:05:00
23300
代码可运行
举报
文章被收录于专栏:学习学习
运行总次数:0
代码可运行

通知类型

上面我们说了什么是通知,接下来学习通知的类型 @Around 就是其中一种通知类型,表示环绕通知

SpringAOP 的通知类型有以下几种:

  • @Around环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  • @Before前置通知,次注解标注的通知方法在方法前被执行
  • @After后置通知,次注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing异常后通知,此注解标注的通知方法发生异常后执行

接下来我们通过代码来加深对这几个通知的理解:

  • 为了方便学习,我们新建一个项目
代码语言:javascript
代码运行次数:0
运行
复制
import lombok.extern.slf4j.Slf4j;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.*;  
import org.springframework.stereotype.Component;  
  
@Slf4j  
@Aspect  
@Component  
public class AspectDemo {  
    // 前置通知  
    @Before("execution(* com.example.demo.controller.*.*(..))")  
    public void doBefore() {  
        log.info("执行 Before 方法");  
    }  
  
    // 后置通知  
    @After("execution(* com.example.demo.controller.*.*(..))")  
    public void doAfter(){  
        log.info("执行 After 方法");  
    }  
  
    // 返回后通知  
    @AfterReturning("execution(* com.example.demo.controller.*.*(..))")  
    public void doAfterReturning() {  
        log.info("执行 AfterReturning 方法");  
    }  
  
    // 抛出异常后通知  
    @AfterThrowing("execution(* com.example.demo.controller.*.*(..))")  
    public void doAfterThrowing() {  
        log.info("执行 doAfterThrowing 方法");  
    }  
  
    // 添加环绕通知  
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {  
        log.info("Around 方法开始执行");  
        Object result = joinPoint.proceed();  
        log.info("Around 方法执行完毕");  
        return result;  
    }  
}

写一些测试程序:

代码语言:javascript
代码运行次数:0
运行
复制
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
@RequestMapping("/test")  
@RestController  
public class TestController {  
    @RequestMapping("/t1")  
    public String t1() {  
        return "t1";  
    }  
  
    @RequestMapping("/t2")  
    public boolean t2() {  
        int a = 10 / 0;  
        return true;  
    }  
}

运行程序

运行程序,观察日志:

正常运行
  1. 正常运行的情况: http://127.0.0.1:8080/test/t1
image.png
image.png

观察日志:

image.png
image.png
  • 程序正常运行情况下,@AfterThrowing 标识的通知方法不会执行
  • 从图上也可以看出来,@Around 标识的通知方法包含两部分,一个“前置逻辑”,一个“后置逻辑”。
    • 前置逻辑会先于 @Before 标识的通知方法执行
    • 后置逻辑会晚于 @After 标识的通知方法执行
image.png
image.png
运行异常
  1. 异常时的情况: http://127.0.0.1:8080/test/t2
image.png
image.png

观察日志:

image.png
image.png

程序发生异常的情况下:

  • @AfterReturning 标识的通知方法不会执行,@AfterThrowing 标识的通知方法执行了
  • @Around 环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会再执行了(因为原始方法调用出异常了)
image.png
image.png

注意事项

  • @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around 环绕通知方法的返回值,必须指定为 Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的
  • 一个切面类可以有多个切点

@Pointcut

上面代码存在一个问题,就是存在大量重复的切点表达式,execution(* com.example.demo.controller.*.*(..))Spring 提供了 @Pointcgut 注解,把巩固的切点表达式提取出来,需要用到时引用该切入点表达式即可

上述代码就可以修改为:

代码语言:javascript
代码运行次数:0
运行
复制
import lombok.extern.slf4j.Slf4j;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.*;  
import org.springframework.stereotype.Component;  
  
@Slf4j  
@Aspect  
@Component  
public class AspectDemo {  
    // 定义切点(公共的切点表达式)  
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")  
    private void pt() {}  
  
    // 前置通知  
    @Before("pt()")  
    public void doBefore() {  
        log.info("执行 Before 方法");  
    }  
  
    // 后置通知  
    @After("pt()")  
    public void doAfter(){  
        log.info("执行 After 方法");  
    }  
  
    // 返回后通知  
    @AfterReturning("pt()")  
    public void doAfterReturning() {  
        log.info("执行 AfterReturning 方法");  
    }  
  
    // 抛出异常后通知  
    @AfterThrowing("pt()")  
    public void doAfterThrowing() {  
        log.info("执行 doAfterThrowing 方法");  
    }  
  
    // 添加环绕通知  
    @Around("pt()")  
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {  
        log.info("Around 方法开始执行");  
        Object result = joinPoint.proceed();  
        log.info("Around 方法执行完毕");  
        return result;  
    }  
}

当切点定义使用 private 修饰时,仅能在当前切面类中使用,当其他切面类也要使用当前切点定义时,就需要把 private 改为 public。引用方式为:全限定类型.方法名()

代码语言:javascript
代码运行次数:0
运行
复制
import lombok.extern.slf4j.Slf4j;  
import org.aspectj.lang.annotation.Aspect;  
import org.aspectj.lang.annotation.Before;  
import org.springframework.stereotype.Component;  
  
@Slf4j  
@Aspect  
@Component  
public class AspectDemo2 {  
    // 前置通知  
    @Before("com.example.demo.AspectDemo.pt()")  
    public void doBefore() {  
        log.info("执行 AspectDemo2 -> Before 方法");  
    }  
}

@Order(切面优先级)

当我们在一个项目中,定义了多个切面类时,并且这些切面类的多个切入点都匹配到了同一个目标方法 当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?

我们还是通过程序来求证:

  • 定义多个切面类
  • 为了防止干扰,我们把 AspectDemo 这个切面先去掉(把 @Component 注解去掉即可)
  • 为了简化,只写了 @Before@After 两个通知
代码语言:javascript
代码运行次数:0
运行
复制
@Slf4j  
@Aspect  
@Component  
public class AspectDemo2 {  
    // 定义切点(公共的切点表达式)  
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")  
    private void pt() {}  
  
    // 前置通知  
    @Before("pt()")  
    public void doBefore() {  
        log.info("执行 AspectDemo2 -> Before 方法");  
    }  
  
    // 后置通知  
    @After("pt()")  
    public void doAfter() {  
        log.info("执行 AspectDemo2 -> After 方法");  
    }  
}
代码语言:javascript
代码运行次数:0
运行
复制
@Slf4j  
@Aspect  
@Component  
public class AspectDemo3 {  
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")  
    private void pt() {}  
  
    // 前置通知  
    @Before("pt()")  
    public void doBefore() {  
        log.info("执行 AspectDemo3 -> Before 方法");  
    }  
  
    // 后置通知  
    @After("pt()")  
    public void doAfter() {  
        log.info("执行 AspectDemo3 -> After 方法");  
    }  
}
代码语言:javascript
代码运行次数:0
运行
复制
@Slf4j  
@Aspect  
@Component  
public class AspectDemo4 {  
    // 定义切点(公共的切点表达式)  
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")  
    private void pt() {}  
  
    // 前置通知  
    @Before("pt()")  
    public void doBefore() {  
        log.info("执行 AspectDemo4 -> Before 方法");  
    }  
  
    // 后置通知  
    @After("pt()")  
    public void doAfter() {  
        log.info("执行 AspectDemo4 -> After 方法");  
    }  
}

运行程序

访问接口: http://127.0.0.1:8080/test/t1

image.png
image.png

观察日志:

image.png
image.png

通过上述程序的运行结果,可以看出,存在多个切面类时,默认按照切面类的类名字母排序:

  • @Before 通知:字母排名靠前的先执行
  • @After 通知:字母排名靠前的后执行 但这种方式不方便管理,我们的类名更多还是具备一定含义的

Spring 给我们提供了一个新的注解,来控制这些切面通知的执行顺序:@Order

使用方式如下:

代码语言:javascript
代码运行次数:0
运行
复制
@Aspect
@Component
@Order(2)
public class AspectDemo2 {
	//...代码省略
}
代码语言:javascript
代码运行次数:0
运行
复制
@Aspect
@Component
@Order(1)
public class AspectDemo3 {
	//...代码省略
}
代码语言:javascript
代码运行次数:0
运行
复制
@Aspect
@Component
@Order(3)
public class AspectDemo4 {
	//...代码省略
}

重新运行程序,访问接口: http://127.0.0.1:8080/test/t1

image.png
image.png

观察日志:

image.png
image.png

通过上述程序的运行结果,得出结论:@Order 注解标识的切面类,执行顺序如下:

  • @Before 通知:数字越小先执行
  • @After 通知:数字越大先执行

@Order 控制切面的优先级,先执行优先级较高的切面,再执行优先级较低的切面,最终执行目标方法

image.png|376
image.png|376
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-07-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 通知类型
    • 运行程序
      • 正常运行
      • 运行异常
  • @Pointcut
  • @Order(切面优先级)
    • 运行程序
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档