驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
AOP 切面执行顺序的三点记录
/    

AOP 切面执行顺序的三点记录

开篇

AOP在Spring项目中相当常见,能够无侵入的实现不少功能:

  • 日志记录
  • 权限认证
  • 异常处理
  • ......

在AOP中有好几个方法,分别为:@Around@Before@After@AfterReturing@AfterThrowing

该文主要探讨3个问题:

  1. 单个切面中核心方法执行顺序?

  2. 如何控制多个切面的执行顺序?

  3. 多个切面中方法的执行顺序?

单个切面中核心方法执行顺序

代码可以很好的验证上述问题,有代码如下:

package com.mc.aopstudy.aspect;

import cn.hutool.core.convert.Convert;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 所有请求的切面
 *
 * @author LiuChunfu
 * @date 2018/2/7
 */
@Aspect
@Component
public class RequestAspect {

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void pointCut() {
    }

    /**
     * 当定义一个Around增强处理方法时,该方法的第一个形参必须是ProceedJoinPoint类型(至少含有一个形参),在增强处理方法体内,调用ProceedingJoinPoint参数的procedd()方法才会执行目标方法——这就是Around增强处理可以完全控制方法的执行时机、如何执行的关键;如果程序没有调用ProceedingJoinPoint参数的proceed()方法,则目标方法不会被执行。下面定义一个Around增强处理。
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("R1-around-start");
        //此处必须将结果获取后返回才会正常对代码进行执行
        Object obj = joinPoint.proceed();
        System.out.println("R1-around-end");
        return Convert.toStr(obj) + "123";
    }

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("R1-before");
    }

    /**
     * After增强处理不管目标方法如何结束(包括成功完成和遇到异常中止两种情况),它都会被织入。
     */
    @After("pointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("R1-after");
    }

    @AfterReturning("pointCut()")
    public void afterReturing(JoinPoint point) {
        System.out.println("R1-afterReturing");
        //return returnValue;
    }

    /**
     * 怎么获取是哪一类异常?
     * @param joinPoint
     */
    @AfterThrowing("pointCut()ut()")
    public void afterThrowing(JoinPoint joinPoint) {
        System.out.println("R1-afterThrowing");
    }
}

执行结果:

R1-around-start
R1-before
method invoke
R1-around-end
R1-after
R1-afterReturing

其中 method invoke 是 调用的被切的方法的输出结果。

经过分析很容易分析出:

Around Start → Start → Invoke → Around End → After → After Returing

可以分为如下规则记忆:

  1. Around 最早开始
  2. Returing 最后
  3. Before 肯定在After之前

如何控制多个AOP 切面的执行顺序

如果我有多个切面,如何控制这几个切面执行的先后顺序了?

有如下2种比较好方法:

第一种:注解(推荐)

@Order(100)
@Aspect
@Component
public class RequestAspect {}

上述代码种新增了一个注解是 @Order(value)其中value的值越小,执行顺序越靠前。

该方式最简单,推荐!

第二种:实现接口

import org.springframework.core.Ordered;

@Aspect
@Component
public class RequestAspect implements Ordered {

    @Override
    public int getOrder() {
        return 0;
    }
}

实现接口org.springframework.core.Ordered 种的getOrder()方法。也是value值越小,执行顺序越靠前。

同一个方法的多个切面中方法的执行顺序

对于同一个方法,可能会有多个切面对其进行处理,那么其顺序如何了?

还是通过代码进行处理,下面有2部分代码:

切面1的代码:

package com.mc.aopstudy.aspect;

import cn.hutool.core.convert.Convert;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(1)
@Aspect
@Component
public class RequestAspect1 {


    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("R1-around-start");
        Object obj = joinPoint.proceed();
        System.out.println("R1-around-end");
        return Convert.toStr(obj) + "123";
    }

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("R1-before");
    }


    @After("pointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("R1-after");
    }

    @AfterReturning("pointCut()")
    public void afterReturing(JoinPoint point) {
        System.out.println("R1-afterReturing");
        //return returnValue;
    }

    @AfterThrowing("pointCut()")
    public void afterThrowing(JoinPoint joinPoint) {
        System.out.println("R1-afterThrowing");
    }


}

切面2的代码:

package com.mc.aopstudy.aspect;

import cn.hutool.core.convert.Convert;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(100)
public class RequestAspect2  {

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("R2-around-start");
        Object obj = joinPoint.proceed();
        System.out.println("R2-around-end");
        return Convert.toStr(obj) + "123";
    }

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("R2-before");
    }

    @After("pointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("R2-after");
    }

    @AfterReturning("pointCut()")
    public void afterReturing(JoinPoint point) {
        System.out.println("R2-afterReturing");
    }

    @AfterThrowing("pointCut()")
    public void afterThrowing(JoinPoint joinPoint) {
        System.out.println("R2-afterThrowing");
    }
}

执行结果是什么了?

R1-around-start
R1-before
R2-around-start
R2-before
method invoke
R2-around-end
R2-after
R2-afterReturing
R1-around-end
R1-after
R1-afterReturing

从结果来分析:

  1. R1早于R2执行:因为R1的order值为1,R2的order值为2。
  2. 执行顺序同单个切面中核心方法执行顺序 中的顺序保持一致:around start、before、around end、after、afterReturning
  3. R1执行Around的时候把R2当成1个整体进行,当R2执行完毕后,才会执行R1后续的。
  4. 对于第3点可以从R1在around start之后的就开始执行R2部分了,直到R2部分执行完成后才返回去执行R1后续代码看出来。

用一个图对此进行表示如下:

多个切面执行顺序.png

骐骥一跃,不能十步。驽马十驾,功在不舍。