Spring AOP通知方法的執(zhí)行順序及說(shuō)明
前言
我們知道可以通過(guò)注解的方式來(lái)定義通知方法,如 @Before,@After 等,那么當(dāng)執(zhí)行連接點(diǎn)方法的時(shí)候這些通知方法的執(zhí)行的執(zhí)行順序是什么呢?
本篇文章就是探討這個(gè)問(wèn)題的,關(guān)于 advisor, pointCut,切面等定義以及生成 advisor 的時(shí)間點(diǎn)前面文章都已經(jīng)介紹過(guò),所以本篇只討論 advisor 數(shù)組排序相關(guān)的內(nèi)容。
第一次排序,遍歷切面方法時(shí)排序
我們知道,生成 advisor 實(shí)例首先要解析切面類,更準(zhǔn)確的說(shuō)是解析通知方法上的注解,根據(jù)不同的注解生成不同的 pointCut 進(jìn)而生成不同的 advisor 實(shí)例,所以先遍歷哪個(gè)方法,就先生成該方法對(duì)應(yīng)的 advisor 然后依次放入 advisor 數(shù)組中, 可以理解為這是對(duì) advisor 數(shù)組的第一次排序。
private List<Method> getAdvisorMethods(Class<?> aspectClass) {
final List<Method> methods = new ArrayList<>();
ReflectionUtils.doWithMethods(aspectClass, method -> {
// Exclude pointcuts
if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
methods.add(method);
}
}, ReflectionUtils.USER_DECLARED_METHODS);
if (methods.size() > 1) {
methods.sort(METHOD_COMPARATOR);
}
return methods;
}其實(shí)上段獲取排序的方法的邏輯很簡(jiǎn)單,就是通過(guò)反射獲取到通知方法。此外,因?yàn)?@Pointcut 只是切點(diǎn),并不是一個(gè)增強(qiáng)方法,所以不需要生成 advisor, 所以這里就將它排除了。然后我們發(fā)現(xiàn)排序使用了 METHOD_COMPARATOR, 所以具體怎么排序還是由該排序器決定的,接下來(lái)我們看一下它是如何實(shí)例化以及如何排序的。可以先看下面的流程圖。

通過(guò)上面流程圖我們知道,該排序比較器首先是按照 Around, Before, After, AfterReturning, AfterThrowing 等注解類型進(jìn)行排序的,如果有相同類型的通知方法,再按照方法名稱進(jìn)行排序。
如此一來(lái),后面在遍歷通知方法生成 advisor 實(shí)例的時(shí)候也會(huì)按照這個(gè)順序一個(gè)一個(gè)放入 advisor 列表中。
第二次排序,獲取到跟當(dāng)前bean匹配的advisor列表之后
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
//對(duì)跟當(dāng)前 bean 匹配的 advisor 列表進(jìn)行排序
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
// AspectJAwareAdvisorAutoProxyCreator 重寫(xiě)的 sortAdvisors 方法
protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors = new ArrayList<>(advisors.size());
for (Advisor advisor : advisors) {
partiallyComparableAdvisors.add(
new PartiallyComparableAdvisorHolder(advisor, DEFAULT_PRECEDENCE_COMPARATOR));
}
List<PartiallyComparableAdvisorHolder> sorted = PartialOrder.sort(partiallyComparableAdvisors);
if (sorted != null) {
List<Advisor> result = new ArrayList<>(advisors.size());
for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
result.add(pcAdvisor.getAdvisor());
}
return result;
}
else {
return super.sortAdvisors(advisors);
}
}因?yàn)楸酒恼轮饕怯懻撆判颍灾会槍?duì)排序的內(nèi)容,從上面代碼可以看出來(lái),對(duì) advisor 列表的排序主要是由DEFAULT_PRECEDENCE_COMPARATOR 決定的,下面我們還是用流程圖的方式看一下它是的排序規(guī)則。
圖片可能看不清,流程圖鏈接如下:
https://www.processon.com/view/link/614ff2e90e3e74520cb6c47d

這個(gè)排序比較器的流程看著挺復(fù)雜,但是總結(jié)下來(lái)還是很簡(jiǎn)單的,Spring 對(duì) advisor 列表排序所用的排序器為AspectJPrecedenceComparator,但是它其實(shí)是依賴advisorComparator 進(jìn)行排序,advisorComparator 是AnnotationAwareOrderComparator 類型的排序器,它的排序規(guī)則如下
- 首先嘗試根據(jù)是否實(shí)現(xiàn)了 PriorityOrdered 接口排序,實(shí)現(xiàn)該接口的優(yōu)先
- 如果實(shí)現(xiàn)了Ordered 接口,則需要重寫(xiě) getOrder() 方法,根據(jù)該方法返回值確定排序順序。如果對(duì)象類上標(biāo)注了 @Order 注解,則按照注解的屬性值排序。否則返回 Ordered.LOWEST_PRECEDENCE 默認(rèn)排在最后。
如果借助AnnotationAwareOrderComparator 類型的排序器沒(méi)有排好順序(存在order定義相同的情況),但是多個(gè)不同的 advisor 又在同一個(gè)切面,AspectJPrecedenceComparator 會(huì)調(diào)用 comparePrecedenceWithinAspect 通過(guò)declarationOrder 再次排序,這里需要注意一下,這個(gè) declarationOrder 本來(lái)的意思是代表通知方法在切面中的聲明順序,根據(jù)這個(gè)順序可以決定是先聲明的通知先執(zhí)行或者后執(zhí)行。
只不過(guò)從 jdk7 之后,不支持通過(guò)類信息獲取到聲明順序了,所以Spring 在實(shí)例化通過(guò)注解生成的 advisor 的時(shí)候都給設(shè)置成了 0,所以所有的advisor聲明順序都一樣。那么這一步其實(shí)也就不生效了,并不會(huì)影響排序結(jié)果。
當(dāng)同一個(gè)切面里面有多個(gè)相同類型的通知方法的時(shí)候,先執(zhí)行哪個(gè)呢?
考慮到前面討論的兩步排序都沒(méi)有處理這種情況,那么影響相同類型通知方法執(zhí)行順序的地方就只有獲取通知方法的時(shí)候,先獲取到哪個(gè)方法就會(huì)先生成對(duì)應(yīng)的 advisor, 也就會(huì)先執(zhí)行。
解析切面獲取類中聲明方法所調(diào)用的接口為Method[] declaredMethods = clazz.getDeclaredMethods();該方法返回的方法數(shù)組的順序是按照由代碼里聲明的順序決定的,所以在切面類中,那個(gè)通知在前面,就會(huì)先執(zhí)行哪一個(gè)。
一般情況下各個(gè)通知方法的執(zhí)行順序
一般情況下我們也不會(huì)在同一個(gè)類中聲明多個(gè)相同類型的增強(qiáng)方法,假設(shè)我們切面類現(xiàn)在 Around, Before, After, AfterReturning, AfterThrowing 這五種類型的通知方法各有一個(gè),那么通過(guò)第一步排序我們獲得的就是Around->Before->After->AfterReturning->AfterThrowing 這個(gè)順序,此外注解類型的通知實(shí)例化出的 advisor 類型為 InstantiationModelAwarePointcutAdvisorImpl,它實(shí)現(xiàn)了 Order 接口,可以通過(guò)在切面也實(shí)現(xiàn) Ordered 接口進(jìn)而影響 advisor 在第二步排序的順序。
但是因?yàn)闆](méi)法單獨(dú)對(duì)某一個(gè)通知方法設(shè)置 order, 所以通過(guò)給切面設(shè)置 order, 最后作用到這幾個(gè)通知方法的效果是一樣的,比如都變成了 1 或者都變成1000000,如果不設(shè)置的話,默認(rèn)為Ordered.LOWEST_PRECEDENCE, 即最小優(yōu)先級(jí)。
但無(wú)論是什么優(yōu)先級(jí),并不會(huì)影響這幾個(gè)通知方法之間的執(zhí)行順序。
總結(jié)
目前為止,我們應(yīng)該已經(jīng)知道了 AOP 各種類型的增強(qiáng)方法的執(zhí)行順序是怎么樣的了,即Around->Before->After->AfterReturning->AfterThrowing??墒菫槭裁匆@么設(shè)計(jì)呢?
這跟各個(gè)通知方法的增強(qiáng)邏輯有關(guān),下一篇文章將探討被代理對(duì)象執(zhí)行切點(diǎn)方法時(shí)各個(gè)增強(qiáng)方法的執(zhí)行流程。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于maven依賴 ${xxx.version}報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了關(guān)于maven依賴 ${xxx.version}報(bào)錯(cuò)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java?IO?API實(shí)現(xiàn)獲取路徑信息并去除冗余信息
這篇文章主要為大家詳細(xì)介紹了Java?IO?API實(shí)現(xiàn)獲取路徑信息并去除冗余信息,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2026-03-03
Springboot maven plugin插件原理及作用
這篇文章主要介紹了Springboot maven plugin插件原理及作用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
Java查找不重復(fù)無(wú)序數(shù)組中是否存在兩個(gè)數(shù)字的和為某個(gè)值
今天小編就為大家分享一篇關(guān)于Java查找不重復(fù)無(wú)序數(shù)組中是否存在兩個(gè)數(shù)字的和為某個(gè)值,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
Springboot整合quartz產(chǎn)生錯(cuò)誤及解決方案
這篇文章主要介紹了Springboot整合quartz產(chǎn)生錯(cuò)誤及解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
Maven插件構(gòu)建Docker鏡像的實(shí)現(xiàn)步驟
這篇文章主要介紹了Maven插件構(gòu)建Docker鏡像的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10

