Spring AOP多个切面指定执行顺序

一.背景

一个需求需要在现有系统中增加一个切面,系统中原来也有几个切面类,且没有显式的指定其执行顺序,我想让我新加的切面在所有切面最后执行。

本文将介绍一个切面中各个通知类型的执行顺序、多个切面类默认执行顺序以及如何指定其执行顺序、多个切面下通知方法执行顺序。

二.一个切面下多个通知方法执行顺序

Around(执行目标方法前部分)   ->   Before   ->   AfterReturning(正常返回)/AfterThrowing(抛出异常)   ->   After   ->   Around(执行目标方法后部分)

测试代码:

package com.fkp.aspect;

import com.alibaba.fastjson.JSON;
import com.fkp.controller.TargetTestController;
import com.fkp.entity.User;
import com.fkp.exception.BusinessException;
import com.fkp.param.BaseResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 切面类,需要加入到容器中
 */
@Aspect
@Component
@Slf4j
@Order(1)
public class MyAspect {

    /**
     * 定义切点
     * execution(访问权限 方法返回值 方法声明(参数) 异常类型);
     *      *代表任意个数
     *      ..用在方法参数中,表示任意多个参数,用在包名后,表示当前包及其子包路径
     *      +用在类名后,表示当前类及其子类,用在接口后,表示当前接口及其实现类
     */
    @Pointcut(value = "execution(public * com.fkp.controller.Target*.*(..))")
    public void myPointCut(){
    }

    /**
     * 定义前置增强
     *
     * @param joinPoint 可以获得目标方法的信息
     */
    @Before(value = "myPointCut()")
    public void beforeAdvice(JoinPoint joinPoint){
        log.info("----------------------------Before----------------------------");
        //拿到目标方法的参数列表
        Object[] args = joinPoint.getArgs();
        log.info("method args: {}", args);
        //拿到方法的签名信息,可以通过其获取方法名
        Signature signature = joinPoint.getSignature();
        String methodName = signature.getName();
        log.info("method name: {}", methodName);
        //拿到目标方法所在的类对象,可以调用其方法
//        Object target = joinPoint.getTarget();
//        TargetTestController targetObject = (TargetTestController) target;
//        BaseResponse<User> response = targetObject.getUserById("002");
//        log.info("targetObjectInvocationResponse{}", response);
    }

    /**
     * 后置增强,方法正常执行完成后执行,若发生异常则不执行
     * 参数列表需要在注解属性argNames中标明参数名和顺序,returning属性指定接收目标方法返回值的参数名,JoinPoint需在Object前
     *
     * @param result 获得目标方法执行结果
     * @param joinPoint 用来获取目标方法的信息
     */
    @AfterReturning(value = "myPointCut()", returning = "result", argNames = "joinPoint,result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result){
        log.info("----------------------------AfterReturning----------------------------");
        //获取目标方法执行结果
        log.info("result: {}", result);
        //获取目标方法执行结果的类型
        log.info("resultType: {}", result.getClass());
        //获取目标方法的参数列表
        log.info("method args: {}", joinPoint.getArgs());
    }

    /**
     * 环绕增强,可以在方法前和方法后增强,需要手动调用ProceedingJoinPoint的proceed方法执行目标方法并拿到返回值并返回
     *
     * @param joinPoint 可以获得目标方法的信息以及执行目标方法,ProceedingJoinPoint实现JoinPoint
     * @return 目标方法实际的返回值
     * @throws Throwable ProceedingJoinPoint的proceed方法抛出Throwable类型的异常
     */
    @Around(value = "myPointCut()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("----------------------------Around Before----------------------------");
        //拿到目标方法参数列表
        Object[] args = joinPoint.getArgs();
        log.info("method args: {}",args);
        //拿到目标方法名
        String methodName = joinPoint.getSignature().getName();
        log.info("method name: {}", methodName);
        if("getUserById".equals(methodName)){
//            args[0] = "002";
        }
        //执行目标方法并传入参数
        Object response = joinPoint.proceed(args);
        log.info("----------------------------Around After----------------------------");
        log.info("target method result: {}", response);

        //若已知返回值的类型可以进行强转并获取其中的属性值
        if(response instanceof BaseResponse){
            BaseResponse<?> baseResponse = (BaseResponse<?>) response;
            log.info("response status: {}", baseResponse.getStatus());
        }

        //设置目标方法的返回值
        return response;
    }

    /**
     * 异常增强,在目标方法抛出异常时执行,可用于异常监控,执行此方法后进入全局异常处理器
     * @AfterThrowing throwing属性指定方法中Exception类型参数名
     *
     * @param joinPoint 可以获取目标方法的信息
     * @param e 发生异常的异常对象
     */
    @AfterThrowing(value = "myPointCut()", throwing = "e", argNames = "joinPoint,e")
    public void throwingAdvice(JoinPoint joinPoint, Exception e){
        log.info("----------------------------AfterThrowing----------------------------");
        //获取目标方法的方法名
        String methodName = joinPoint.getSignature().getName();
        //获取发生异常的异常信息
        String errorMessage = e.getMessage();
        log.error("methodName: {},exception message: {}", methodName, errorMessage);

        //可以对特定的异常进行处理
        if(e instanceof BusinessException){
            log.error("business exception message: {}", e.getMessage());
        }
    }

    /**
     * 最终增强,目标方法执行后执行,总是执行无论是否发生异常,若发生异常则该方法执行后进入全局异常处理器
     *
     * @param joinPoint 可以获取目标方法的信息
     */
    @After(value = "myPointCut()")
    public void afterAdvice(JoinPoint joinPoint){
        log.info("----------------------------After----------------------------");
        //获取参数列表
        Object[] args = joinPoint.getArgs();
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        log.info("method name: {},args: {}",methodName, args);
    }
}

执行结果

三.多个切面默认执行顺序以及指定其执行顺序

1.默认执行顺序

多个切面默认执行顺序为按照bean的名称字母排序例如AspectA  >  AspectB

2.如何指定其执行顺序

①.通过 @Order注解

@Order注解的value属性指定各个切面的执行顺序,value值默认为Integer的最大值,value越小优先级越高,因此Order注解默认优先级为最低,通过为多个切面使用@Order注解的value值指定其优先级。

若两个或多个切面通过@Order注解标注且value值相同则又会通过默认的按bean名称字母排序

②通过@Primary或@Priority指定

参考http://t.csdn.cn/j1io8

 四.多个切面下各切面的通知方法执行顺序

以两个切面为例,例如有两个切面A1、A2,执行顺序为,优先级A1 > A2

A1.Around(执行目标方法前部分)  ->  A1.Before  ->  A2.Around(执行目标方法前部分)  ->  A2.Before  ->  A2.AfterReturning(正常返回)/AfterThrowing(抛出异常)  ->  A2.After  ->  A2.Around(执行目标方法后部分)  ->  A1.AfterReturning(正常返回)/AfterThrowing(抛出异常)  ->  A1.After  ->  A1.Around(执行目标方法后部分)

测试代码(切面代码为第二章节中的切面,复制了两份)

执行结果(切面Aspect,Aspect2,优先级Aspect > Aspect2)

 注:Spring AOP新版本和旧版本的个通知类型方法执行顺序存在差异,具体参考:http://t.csdn.cn/KiMep

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值