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

2026-02-11 19:38:47 5253

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

Spring 中 AOP 的通知类型有以下几种:

@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行@Before:前置通知,次注解标注的通知方法在方法前被执行@After:后置通知,次注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行接下来我们通过代码来加深对这几个通知的理解:

为了方便学习,我们新建一个项目代码语言:javascript复制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复制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;

}

}运行程序运行程序,观察日志:

正常运行正常运行的情况: http://127.0.0.1:8080/test/t1

image.png观察日志:

image.png程序正常运行情况下,@AfterThrowing 标识的通知方法不会执行从图上也可以看出来,@Around 标识的通知方法包含两部分,一个“前置逻辑”,一个“后置逻辑”。 前置逻辑会先于 @Before 标识的通知方法执行后置逻辑会晚于 @After 标识的通知方法执行image.png运行异常异常时的情况: 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 注解,把巩固的切点表达式提取出来,需要用到时引用该切入点表达式即可

上述代码就可以修改为:

代码语言:javascript复制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复制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复制@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复制@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复制@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

使用方式如下:

代码语言:javascript复制@Aspect

@Component

@Order(2)

public class AspectDemo2 {

//...代码省略

}代码语言:javascript复制@Aspect

@Component

@Order(1)

public class AspectDemo3 {

//...代码省略

}代码语言:javascript复制@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