1. 程式人生 > >spring aop annotation execute order

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