聊聊Spring AOP @Before @Around @After等advice的執(zhí)行順序
用過(guò)spring框架進(jìn)行開(kāi)發(fā)的人,多多少少會(huì)使用過(guò)它的AOP功能,都知道有@Before、@Around和@After等advice。
最近,為了實(shí)現(xiàn)項(xiàng)目中的輸出日志和權(quán)限控制這兩個(gè)需求,我也使用到了AOP功能。
我使用到了@Before、@Around這兩個(gè)advice。但在,使用過(guò)程中,卻對(duì)它們的執(zhí)行順序并不清楚。
為了弄清楚在不同情況下,這些advice到底是以怎么樣的一個(gè)順序進(jìn)行執(zhí)行的,我作了個(gè)測(cè)試,在此將其記錄下來(lái),以供以后查看。
前提對(duì)于AOP相關(guān)類(lèi)(aspect、pointcut等)的概念,本文不作說(shuō)明。
對(duì)于如何讓spring框架掃描到AOP,本文也不作說(shuō)明。
情況一: 一個(gè)方法只被一個(gè)Aspect類(lèi)攔截當(dāng)一個(gè)方法只被一個(gè)Aspect攔截時(shí),這個(gè)Aspect中的不同advice是按照怎樣的順序進(jìn)行執(zhí)行的呢?請(qǐng)看:
添加 PointCut類(lèi)該pointcut用來(lái)攔截test包下的所有類(lèi)中的所有方法。
package test;import org.aspectj.lang.annotation.Pointcut;public class PointCuts { @Pointcut(value = 'within(test.*)') public void aopDemo() { }}添加Aspect類(lèi)
該類(lèi)中的advice將會(huì)用到上面的pointcut,使用方法請(qǐng)看各個(gè)advice的value屬性。
package test;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Component@Aspectpublic class Aspect1 { @Before(value = 'test.PointCuts.aopDemo()') public void before(JoinPoint joinPoint) { System.out.println('[Aspect1] before advise'); } @Around(value = 'test.PointCuts.aopDemo()') public void around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println('[Aspect1] around advise 1'); pjp.proceed(); System.out.println('[Aspect1] around advise2'); } @AfterReturning(value = 'test.PointCuts.aopDemo()') public void afterReturning(JoinPoint joinPoint) { System.out.println('[Aspect1] afterReturning advise'); } @AfterThrowing(value = 'test.PointCuts.aopDemo()') public void afterThrowing(JoinPoint joinPoint) { System.out.println('[Aspect1] afterThrowing advise'); } @After(value = 'test.PointCuts.aopDemo()') public void after(JoinPoint joinPoint) { System.out.println('[Aspect1] after advise'); }}添加測(cè)試用Controller
添加一個(gè)用于測(cè)試的controller,這個(gè)controller中只有一個(gè)方法,但是它會(huì)根據(jù)參數(shù)值的不同,會(huì)作出不同的處理:一種是正常返回一個(gè)對(duì)象,一種是拋出異常(因?yàn)槲覀円獪y(cè)試@AfterThrowing這個(gè)advice)
package test;import test.exception.TestException;import org.springframework.http.HttpStatus;import org.springframework.web.bind.annotation.*;@RestController@RequestMapping(value = '/aop')public class AopTestController { @ResponseStatus(HttpStatus.OK) @RequestMapping(value = '/test', method = RequestMethod.GET) public Result test(@RequestParam boolean throwException) { // case 1 if (throwException) { System.out.println('throw an exception'); throw new TestException('mock a server exception'); } // case 2 System.out.println('test OK'); return new Result() {{ this.setId(111); this.setName('mock a Result'); }}; } public static class Result { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }}
測(cè)試 正常情況
在瀏覽器直接輸入以下的URL,回車(chē):
http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=false
我們會(huì)看到輸出的結(jié)果是:
[Aspect1] around advise 1[Aspect1] before advisetest OK[Aspect1] around advise2[Aspect1] after advise[Aspect1] afterReturning advise
測(cè)試 異常情況
在瀏覽器中直接輸入以下的URL,回車(chē):
http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=true
我們會(huì)看到輸出的結(jié)果是:
[Aspect1] around advise 1[Aspect1] before advisethrow an exception[Aspect1] after advise[Aspect1] afterThrowing advise結(jié)論
在一個(gè)方法只被一個(gè)aspect類(lèi)攔截時(shí),aspect類(lèi)內(nèi)部的 advice 將按照以下的順序進(jìn)行執(zhí)行:
正常情況:

異常情況:

此處舉例為被兩個(gè)aspect類(lèi)攔截。
有些情況下,對(duì)于兩個(gè)不同的aspect類(lèi),不管它們的advice使用的是同一個(gè)pointcut,還是不同的pointcut,都有可能導(dǎo)致同一個(gè)方法被多個(gè)aspect類(lèi)攔截。那么,在這種情況下,這多個(gè)Aspect類(lèi)中的advice又是按照怎樣的順序進(jìn)行執(zhí)行的呢?請(qǐng)看:
pointcut類(lèi)保持不變
添加一個(gè)新的aspect類(lèi)package test;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Component@Aspectpublic class Aspect2 { @Before(value = 'test.PointCuts.aopDemo()') public void before(JoinPoint joinPoint) { System.out.println('[Aspect2] before advise'); } @Around(value = 'test.PointCuts.aopDemo()') public void around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println('[Aspect2] around advise 1'); pjp.proceed(); System.out.println('[Aspect2] around advise2'); } @AfterReturning(value = 'test.PointCuts.aopDemo()') public void afterReturning(JoinPoint joinPoint) { System.out.println('[Aspect2] afterReturning advise'); } @AfterThrowing(value = 'test.PointCuts.aopDemo()') public void afterThrowing(JoinPoint joinPoint) { System.out.println('[Aspect2] afterThrowing advise'); } @After(value = 'test.PointCuts.aopDemo()') public void after(JoinPoint joinPoint) { System.out.println('[Aspect2] after advise'); }}
測(cè)試用Controller也不變
還是使用上面的那個(gè)Controller。但是現(xiàn)在 aspect1 和 aspect2 都會(huì)攔截該controller中的方法。
下面繼續(xù)進(jìn)行測(cè)試!
測(cè)試 正常情況
在瀏覽器直接輸入以下的URL,回車(chē):
http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=false
我們會(huì)看到輸出的結(jié)果是:
[Aspect2] around advise 1[Aspect2] before advise[Aspect1] around advise 1[Aspect1] before advisetest OK[Aspect1] around advise2[Aspect1] after advise[Aspect1] afterReturning advise[Aspect2] around advise2[Aspect2] after advise[Aspect2] afterReturning advise
但是這個(gè)時(shí)候,我不能下定論說(shuō) aspect2 肯定就比 aspect1 先執(zhí)行。
不信?你把服務(wù)務(wù)器重新啟動(dòng)一下,再試試,說(shuō)不定你就會(huì)看到如下的執(zhí)行結(jié)果:
[Aspect1] around advise 1[Aspect1] before advise[Aspect2] around advise 1[Aspect2] before advisetest OK[Aspect2] around advise2[Aspect2] after advise[Aspect2] afterReturning advise[Aspect1] around advise2[Aspect1] after advise[Aspect1] afterReturning advise
也就是說(shuō),這種情況下, aspect1 和 aspect2 的執(zhí)行順序是未知的。那怎么解決呢?不急,下面會(huì)給出解決方案。
測(cè)試 異常情況
在瀏覽器中直接輸入以下的URL,回車(chē):
http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=true
我們會(huì)看到輸出的結(jié)果是:
[Aspect2] around advise 1[Aspect2] before advise[Aspect1] around advise 1[Aspect1] before advisethrow an exception[Aspect1] after advise[Aspect1] afterThrowing advise[Aspect2] after advise[Aspect2] afterThrowing advise
同樣地,如果把服務(wù)器重啟,然后再測(cè)試的話(huà),就可能會(huì)看到如下的結(jié)果:
[Aspect1] around advise 1[Aspect1] before advise[Aspect2] around advise 1[Aspect2] before advisethrow an exception[Aspect2] after advise[Aspect2] afterThrowing advise[Aspect1] after advise[Aspect1] afterThrowing advise
也就是說(shuō),同樣地,異常情況下, aspect1 和 aspect2 的執(zhí)行順序也是未定的。
那么在 情況二 下,如何指定每個(gè) aspect 的執(zhí)行順序呢?方法有兩種:
實(shí)現(xiàn)org.springframework.core.Ordered接口,實(shí)現(xiàn)它的getOrder()方法
給aspect添加@Order注解,該注解全稱(chēng)為:org.springframework.core.annotation.Order
不管采用上面的哪種方法,都是值越小的 aspect 越先執(zhí)行。
比如,我們?yōu)?apsect1 和 aspect2 分別添加 @Order 注解,如下:
@Order(5)@Component@Aspectpublic class Aspect1 { // ...}@Order(6)@Component@Aspectpublic class Aspect2 { // ...}
這樣修改之后,可保證不管在任何情況下, aspect1 中的 advice 總是比 aspect2 中的 advice 先執(zhí)行。
如下圖所示:

如果在同一個(gè) aspect 類(lèi)中,針對(duì)同一個(gè) pointcut,定義了兩個(gè)相同的 advice(比如,定義了兩個(gè) @Before),那么這兩個(gè) advice 的執(zhí)行順序是無(wú)法確定的,哪怕你給這兩個(gè) advice 添加了 @Order 這個(gè)注解,也不行。這點(diǎn)切記。
對(duì)于@Around這個(gè)advice,不管它有沒(méi)有返回值,但是必須要方法內(nèi)部,調(diào)用一下 pjp.proceed();否則,Controller 中的接口將沒(méi)有機(jī)會(huì)被執(zhí)行,從而也導(dǎo)致了 @Before這個(gè)advice不會(huì)被觸發(fā)。
比如,我們假設(shè)正常情況下,執(zhí)行順序?yàn)椤盿spect2 -> apsect1 -> controller”,如果,我們把 aspect1中的@Around中的 pjp.proceed();給刪掉,那么,我們看到的輸出結(jié)果將是:
[Aspect2] around advise 1[Aspect2] before advise[Aspect1] around advise 1[Aspect1] around advise2[Aspect1] after advise[Aspect1] afterReturning advise[Aspect2] around advise2[Aspect2] after advise[Aspect2] afterReturning advise
從結(jié)果可以發(fā)現(xiàn), Controller 中的 接口 未被執(zhí)行,aspect1 中的 @Before advice 也未被執(zhí)行。
參考資料Spring 4.3.2.RELEASE 官方資料:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/
其中,AOP的執(zhí)行順序章節(jié)為:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#aop-ataspectj-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.
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持好吧啦網(wǎng)。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章:
1. Ajax跨域問(wèn)題及解決方案(jsonp,cors)2. 概述IE和SQL2k開(kāi)發(fā)一個(gè)XML聊天程序3. 得到XML文檔大小的方法4. asp.net core項(xiàng)目授權(quán)流程詳解5. 詳解盒子端CSS動(dòng)畫(huà)性能提升6. .NET6打包部署到Windows Service的全過(guò)程7. SpringMVC+Jquery實(shí)現(xiàn)Ajax功能8. jsp的九大內(nèi)置對(duì)象深入講解9. 爬取今日頭條Ajax請(qǐng)求10. CSS可以做的幾個(gè)令你嘆為觀止的實(shí)例分享

網(wǎng)公網(wǎng)安備