一、前言
本文不讲什么是AOP,什么是切面,不知道的自己百度,本文主要是探究 AspectJ 切面注解中五种通知注解的执行顺序,以便于我们在实际开发过程中游刃有余的实现我们的业务。
首先我们知道切面通知注解有以下五种:
- @Before: 前置通知, 在方法执行之前执行
- @After: 后置通知, 在方法执行之后执行
- @AfterRunning: 返回通知, 在方法返回结果之后执行
- @AfterThrowing: 异常通知, 在方法抛出异常之后
- @Around: 环绕通知, 围绕着方法执行
看到上面的描述我们大致也知道了一个执行顺序,但是在正常请求和有异常时具体的执行顺序我详细大家没有经过实测还是不敢确定的。另外如果同一个方法被多个Aspect类拦截呢它的执行顺序是怎样的呢?我想大家心里可能有猜想,但这些都需要我们实实在在的写一个例子来验证它。
二、编写测试代码
Aspect切面类
- /**
- * 测试 AspectJ 切面注解中五种通知注解
- *
- * @author Hoscen
- * @since 2021/5/29 14:36
- */
- @Aspect
- @Component
- @Slf4j
- @Order(1)
- public class TestAspect {
- @Pointcut("execution(* cn.hoscen.test.testAspect.controller.*.*(..))")
- public void pointcut() {
- }
- @Before(value = "pointcut()")
- public void doBefore(JoinPoint jp) {
- log.info(Thread.currentThread().getName() + " doBefore");
- }
- @Around(value = "pointcut()")
- public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
- log.info(Thread.currentThread().getName() + " doAround 1");
- Object result = pjp.proceed();
- log.info(Thread.currentThread().getName() + " doAround 2");
- return result;
- }
- @AfterThrowing(value = "pointcut()", throwing = "exception")
- public void doAfterThrowing(JoinPoint jp, Exception exception) {
- log.info(Thread.currentThread().getName() + " doAfterThrowing");
- log.error("doAfterThrowing",exception);
- }
- @AfterReturning(value = "pointcut()", returning = "result")
- public void doAfterReturning(JoinPoint jp, Object result) {
- log.info(Thread.currentThread().getName() + " doAfterReturning");
- }
- @After(value = "pointcut()")
- public void doAfter(JoinPoint jp) {
- log.info(Thread.currentThread().getName() + " doAfter");
- }
- }
测试Controller
- /**
- * 测试 AspectJ 切面注解中五种通知注解
- *
- * @author Hoscen
- * @since 2021/5/29 14:44
- */
- @RestController
- @RequestMapping(value = "/api/test", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
- public class TestAspectController extends BaseController {
- @RequestMapping(value = "/ok", method = RequestMethod.GET)
- public BaseReturn getOk() {
- return BaseReturn.markSuccess(null);
- }
- @RequestMapping(value = "/error", method = RequestMethod.GET)
- public BaseReturn getError() {
- int a = 0;
- int b = 1;
- LOGGER.info(b/a);
- return BaseReturn.markSuccess(null);
- }
- }
三、执行代码观察结果
1、正常请求
- [INFO ] 2021-05-29 15:19:14.651 cn.hoscen.core.common.aop.TestAspect.doAround(TestAspect.java:34)
- http-apr-9100-exec-4 doAround 1
- [INFO ] 2021-05-29 15:19:14.651 cn.hoscen.core.common.aop.TestAspect.doBefore(TestAspect.java:29)
- http-apr-9100-exec-4 doBefore
- [INFO ] 2021-05-29 15:19:14.653 cn.hoscen.core.common.aop.TestAspect.doAround(TestAspect.java:36)
- http-apr-9100-exec-4 doAround 2
- [INFO ] 2021-05-29 15:19:14.653 cn.hoscen.core.common.aop.TestAspect.doAfter(TestAspect.java:53)
- http-apr-9100-exec-4 doAfter
- [INFO ] 2021-05-29 15:19:14.653 cn.hoscen.core.common.aop.TestAspect.doAfterReturning(TestAspect.java:48)
- http-apr-9100-exec-4 doAfterReturning
2、异常请求
- [INFO ] 2021-05-29 15:19:42.701 cn.hoscen.core.common.aop.TestAspect.doAround(TestAspect.java:34)
- http-apr-9100-exec-6 doAround 1
- [INFO ] 2021-05-29 15:19:42.701 cn.hoscen.core.common.aop.TestAspect.doBefore(TestAspect.java:29)
- http-apr-9100-exec-6 doBefore
- [INFO ] 2021-05-29 15:19:42.707 cn.hoscen.core.common.aop.TestAspect.doAfter(TestAspect.java:53)
- http-apr-9100-exec-6 doAfter
- [INFO ] 2021-05-29 15:19:42.707 cn.hoscen.core.common.aop.TestAspect.doAfterThrowing(TestAspect.java:42)
- http-apr-9100-exec-6 doAfterThrowing
四、得出结论与注意事项
1、执行顺序在正常和异常时有所不同,具体请看第三点的日志或自己把代码拿去跑一跑看一看。
2、对于@Around这个advice,不管它有没有返回值,但是必须要方法内部,调用一下 pjp.proceed();否则,Controller 中的接口将没有机会被执行,从而也导致了 @Before这个advice不会被触发。
3、如果同一个方法被多个Aspect类拦截呢,比如两个aspect,不管正常还是异常情况,aspect1 和 aspect2 的执行顺序都是未定的。可以通过给aspect添加@Order注解(该注解全称为:org.springframework.core.annotation.Order) 来指定顺序,值越小的 aspect 越先执行。具体情况可以下载我的测试源码自己跑一下看看就清楚了(测试同一个方法被多个Aspect类拦截,把切面类拷贝一份改个名和order就可以了),比如正常情况是下面这样
https://blog.csdn.net/hxpjava1/article/details/55504513/