spring aop annotation execute order

我们都知道用spring aop注解方式书写切面类时,通常会遇到这几个注解:

@Before – 在目标方法执行前被执行
@After – 在目标方法执行后执行,无论是抛出异常还是正常返回
@AfterReturning – 在目标方法正常执行后执行,可拦截其返回结果
@AfterThrowing – 在目标方法抛出异常后执行,可拦截具体抛出的异常,并对其做处理
@Around – 环绕通知,在方法执行前后做处理逻辑

注解类型确实不多,也好理解,但是,想过没有,当它们进行混用时会出现什么状况呢,或者多个切面类对同一个切入点执行操作会怎样呢?以下分两种场景讨论其执行顺序

1、同一切面类内部,混用@Before@After@Around@AfterThrowing

贴出示例代码如下:

package com.bilibili.sms.web.aop;
@Aspect
@Component
@Order(value = 2)
public class ApiControllerAspect {

    private final static Logger LOGGER = LoggerFactory.getLogger(ApiControllerAspect.class);

    @Pointcut(value = "execution(* com.bilibili.sms.web.controller.api.*Controller.*(..))")
    public void allMethods() {
    }

    @Around(value = "allMethods()")
    public void around2(ProceedingJoinPoint joinPoint) throws Throwable {
        Long now = System.currentTimeMillis();
        LOGGER.info("around2 has been invoked at time "+now);
        joinPoint.proceed();
        LOGGER.info("hhhhhh");
    }

    @Before(value = "allMethods()&& (args(request,response,respJSON,..)) ")
    public void doBefore(HttpServletRequest request, HttpServletResponse response, JsonObject respJSON){
        String a = request.getRemoteUser();
        LOGGER.info(request.getContextPath()+" ,"+request.getHeader("contentType"));

    }

    @After(value = "allMethods()&& (args(request,response,respJSON,..)) ")
    public void doAfter(HttpServletRequest request, HttpServletResponse response, JsonObject respJSON) {
        String b = request.getRequestURI();
        String c  = request.getRemoteHost();
        String d = request.getRequestedSessionId();
    }

    @AfterReturning(value = "allMethods()&& (args(request,response,respJSON,..)) ")
    public void doAfterReturnning(HttpServletRequest request, HttpServletResponse response, JsonObject respJSON) {
        String b = request.getRequestURI();
        String c  = request.getRemoteHost();
        String d = request.getRequestedSessionId();;

    }
    /**
     * Around
     * 手动控制调用核心业务逻辑,以及调用前和调用后的处理,
     * <p>
     * 注意:当核心业务抛异常后,立即退出,转向AfterAdvice
     * 执行完AfterAdvice,再转到ThrowingAdvice
     */
    @Around(value = "allMethods() && (args(request,response,respJSON,..))")
    public void around(ProceedingJoinPoint point,
                       HttpServletRequest request, HttpServletResponse response, JsonObject respJSON)
            throws Throwable {
        Long now = System.currentTimeMillis();

        String type = request.getParameter("type");
        String callback = request.getParameter("callback");

        try {

            respJSON.addProperty("ts", System.currentTimeMillis());

            point.proceed();
        } catch (NumberFormatException ex) {
            respJSON.addProperty("code", ApiErrorCode.CODE_REQ_ERROR);
            respJSON.addProperty("message", "number param invalid");
            LOGGER.error("", ex);
            throw ex;
        } finally {
            //记录api日志
            Long cost = System.currentTimeMillis() - now;
            String respJSONStr = respJSON.toString();

            String logStr = LogHelper.getLog(request, respJSON.toString(), point.getSignature().getName(), cost);
        }
    }

    /**
     * 核心业务逻辑调用异常退出后,执行此Advice,处理错误信息
     * <p>
     * 注意:执行顺序在Around Advice之后
     */
    @AfterThrowing(value = "allMethods() && (args(request,response,respJSON,..))", throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, HttpServletRequest request, HttpServletResponse response, JsonObject respJSON, Throwable exception) throws Throwable {
        //记录异常日志
        LOGGER.error("服务端异常,api:{}.{}", new Object[]{joinPoint.getTarget().
                getClass().getName(), joinPoint.getSignature().getName()}, exception);
//some logic business
    }

}

示例代码中给出了两个@Around注解,@Before@After@AfterReturningAfterThrowing注解各一个。其中around2方法为全量拦截,around方法为精确拦截。拦截的目标方法如下:

public void sendSms(HttpServletRequest request,
                        HttpServletResponse response, JsonObject respJSON,
                        @RequestParam(value = "data", required = false) String data,
                        @RequestParam(value = "appkey", required = true) String appkey) {
        if (ValidUtils.isNullOrEmpty(data,appkey)) {
            respJSON.addProperty(ApiErrorCode.MSG_KEY, "缺少请求参数");
            return;
        }
}

经IDEA跟踪实测,得出以上标注注解过后的各方法执行顺序:

请求先进入精准拦截的around方法,在执行到joinPoint.proceed()方法前,跳转到around2方法,当执行到around2的jointPoint.proceed()方法前,跳转到@Before标注的doBefore方法,当doBefore方法执行完毕后,回到around2方法,执行jointPoint.proceed(),于是开始执行拦截的目标方法。目标方法执行完毕后,跳转到@After标注的doAfter方法(@AfterReturning注解标注的方法此时失效),doAfter执行完毕后,回到around2方法,此时joinPoint.proceed()执行完毕,around2方法继续向下执行,执行完毕后跳回最初的around方法joinPoint.proceed()方法前,继续向下执行时,你会惊奇地发现jointPoint.proceed()方法直接被跳过了(充分说明,当存在多个@Around注解时,只会执行其中的一个jointPoint.proceed()方法),继续向下执行执行完毕后,又会跳到doAfter()方法(这个仔细想想其实是可以理解的),最后执行完毕。由于没有抛出异常,因此由@AfterThrowing标注的afterThrowing方法将不会执行(没有触发条件)。

结论:当使用混合注解时,请求优先进入匹配的方法中,倘若每个方法均匹配,则优先进入准确匹配的方法中,在这里,要注意一点,即便@Before标注的方法与@Around注解标注的方法均精确匹配目标方法,依然优先进入@Around标注的方法中,但在目标方法被调用前(即joinPoint.peoceed()方法执行前),会进入到@Before方法标注的方法中(倘若再没有待执行的@Around注解标注的方法),直至执行完毕。当目标方法执行完毕后优先执行@After方法注解标注的方法,执行完毕后回到@Around注解标注的方法,完成余下的操作。

关于相同注解且拦截点也相同的方法,spring是不知道要执行哪一个的(会随机执行一个),那么怎么解决这种问题呢,官网上给出的解放方案有两个,要么实现org.springframework.core.Ordered接口,要么使用@Order注解,并给其赋值,值为整形,且越小优先级越高。附上官网上Advice ordering策略:

Advice ordering

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first “on the way in” (so given two pieces of before advice, the one with highest precedence runs first). “On the way out” from a join point, the highest precedence advice runs last (so given two pieces of after advice, the one with the highest precedence will run second).

When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise the order of execution is undefined. You can control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the aspect class or annotating it with the Order annotation. Given two aspects, the aspect returning the lower value from Ordered.getValue() (or the annotation value) has the higher precedence.

When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each aspect class, or refactor the pieces of advice into separate aspect classes - which can be ordered at the aspect level.

   原文链接spring aop doc

2、不同切面类,拦截点相同,且均用到@Before@After@Around@AfterThrowing 注解

对于此应用场景,我在测试时直接复制了该测试类进行跟踪测试,最后标明,只有当@Order注解标注在类上时,执行的优先级才会生效.譬如下面我给复制的切面2设置的值为1,跟踪测试时该类优先被执行

@Aspect
@Component
@Order(value = 1)
public class ApiControllerAspect2 {

    private final static Logger API_LOGGER = LoggerFactory.getLogger("api");
    private final static Logger LOGGER = LoggerFactory.getLogger(ApiControllerAspect2.class);

    @Autowired
    private AppConfig appConfig;
    //.....
}

最后补充说明一下,可能文中对于某些细节问题没有解释清楚,如果对此有疑问的同学,欢迎留言提出问题,我看到时会给出回复的~:-D

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值