【Spring AOP】通知类型,@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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值