当前位置: 首页 > news >正文

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

文章目录

  • 通知类型
    • 运行程序
      • 正常运行
      • 运行异常
  • @Pointcut
  • @Order(切面优先级)
    • 运行程序

通知类型

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

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

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

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

  • 为了方便学习,我们新建一个项目
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;  }  
}

写一些测试程序:

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

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

运行异常

  1. 异常时的情况: http://127.0.0.1:8080/test/t2
    image.png

观察日志:
image.png

程序发生异常的情况下:

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

注意事项

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

@Pointcut

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

上述代码就可以修改为:

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。引用方式为:全限定类型.方法名()

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 两个通知
@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 方法");  }  
}
@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 方法");  }  
}
@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

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

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

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

使用方式如下:

@Aspect
@Component
@Order(2)
public class AspectDemo2 {//...代码省略
}
@Aspect
@Component
@Order(1)
public class AspectDemo3 {//...代码省略
}
@Aspect
@Component
@Order(3)
public class AspectDemo4 {//...代码省略
}

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

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

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

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

http://www.dtcms.com/a/277358.html

相关文章:

  • 导入 SciPy 的 io 模块
  • CAPL报文信号接收和发送
  • Function CAll和MCP
  • 音视频学习(三十七):pts和dts
  • Web攻防-PHP反序列化原生内置类Exception类SoapClient类SimpleXMLElement
  • archive/tar: unknown file mode ?rwxr-xr-x
  • 数据结构 单链表(1)
  • FlinkSQL通解
  • ClickHouse 分区机制详解:规则、合并与实践指南
  • 中国国内面试基本流程解析
  • 高性能网络模式-Reactor和Preactor
  • office-ai整合excel
  • Spring Boot 集成 Spring Security 完整示例
  • lambdastream深入剖析
  • [办公及工程版浏览器]_Google Chrome 138.0.7204.101全屏启动插件
  • 【Java Stream】基本用法学习
  • Vue 3 TypeScript 接口(Interface)使用
  • 反射内存卡的使用
  • 【Linux系统与网络编程】13:线程同步
  • AWS Lambda Container 方式部署 Flask 应用并通过 API Gateway 提供访问
  • C++ 模板元编程 type_traits
  • RedisJSON 技术揭秘`JSON.ARRTRIM`用窗口裁剪,让数组保持“刚刚好”
  • 5G NR PDCCH之处理流程
  • [Nagios Core] CGI接口 | 状态数据管理.dat | 性能优化
  • k8s存储入门
  • RabbitMQ 之仲裁队列
  • Matplotlib 中 plt.pcolormesh 函数的使用详解
  • 【sql学习之拉链表】
  • 【LLM-Agent】Qwen-Agent智能体框架使用
  • trySend、Channel 和 Flow 的工作原理