【Spring AOP】通知类型,@Pointcut、@Order(切面优先级)
文章目录
- 通知类型
- 运行程序
- 正常运行
- 运行异常
- @Pointcut
- @Order(切面优先级)
- 运行程序
通知类型
上面我们说了什么是通知,接下来学习通知的类型 @Around
就是其中一种通知类型,表示环绕通知。
Spring
中 AOP
的通知类型有以下几种:
@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; }
}
运行程序
运行程序,观察日志:
正常运行
- 正常运行的情况: http://127.0.0.1:8080/test/t1
观察日志:
- 程序正常运行情况下,
@AfterThrowing
标识的通知方法不会执行 - 从图上也可以看出来,
@Around
标识的通知方法包含两部分,一个“前置逻辑”,一个“后置逻辑”。- 前置逻辑会先于
@Before
标识的通知方法执行 - 后置逻辑会晚于
@After
标识的通知方法执行
- 前置逻辑会先于
运行异常
- 异常时的情况: http://127.0.0.1:8080/test/t2
观察日志:
程序发生异常的情况下:
@AfterReturning
标识的通知方法不会执行,@AfterThrowing
标识的通知方法执行了@Around
环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会再执行了(因为原始方法调用出异常了)
注意事项:
@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
观察日志:
通过上述程序的运行结果,可以看出,存在多个切面类时,默认按照切面类的类名字母排序:
@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
观察日志:
通过上述程序的运行结果,得出结论:@Order
注解标识的切面类,执行顺序如下:
@Before
通知:数字越小先执行@After
通知:数字越大先执行
@Order
控制切面的优先级,先执行优先级较高的切面,再执行优先级较低的切面,最终执行目标方法