SpringCloud基礎篇AOP之攔截優先順序詳解

一灰灰發表於2019-03-12

相關文章可以檢視: spring.hhui.top

前面兩篇分別介紹了AOP的基本使用姿勢和一些高階特性,當時還遺留了一個問題沒有說明,即不同的advice,攔截同一個目標方法時,優先順序是怎樣的,本篇博文將進行詳細分析

  • 同一個切面中,不同型別的advice的優先順序
  • 同一個切面中,同一種型別的advice優先順序
  • 不同切面中,同一型別的advice優先順序
  • 不同切面中,不同型別的advice優先順序

I. 統一切面,不同型別ddvice優先順序

在不分析原始碼的前提下,也只能通過實際的case來看優先順序問題了,我們現在設計一下使用例項,通過輸出結果來看對應的優先順序

1. case設計

首先建立被攔截的bean: com.git.hui.boot.aop.order.InnerDemoBean

@Component
public class InnerDemoBean {

    public String print() {
        try {
            System.out.println("in innerDemoBean start!");
            String rans = System.currentTimeMillis() + "|" + UUID.randomUUID();
            System.out.println(rans);
            return rans;
        } finally {
            System.out.println("in innerDemoBean over!");
        }
    }
}
複製程式碼

接下來寫一個切面,裡面定義我們常見的各種advice

對於aop的使用,有疑問的可以參考: 190301-SpringBoot基礎篇AOP之基本使用姿勢小結

@Component
@Aspect
public class OrderAspect {

    @Pointcut("execution(public * com.git.hui.boot.aop.order.*.*())")
    public void point() {
    }

    @Before(value = "point()")
    public void doBefore(JoinPoint joinPoint) {
        System.out.println("do before!");
    }

    @After(value = "point()")
    public void doAfter(JoinPoint joinPoint) {
        System.out.println("do after!");
    }

    @AfterReturning(value = "point()", returning = "ans")
    public void doAfterReturning(JoinPoint joinPoint, String ans) {
        System.out.println("do after return: " + ans);
    }

    @Around("point()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            System.out.println("do in around before");
            return joinPoint.proceed();
        } finally {
            System.out.println("do in around over!");
        }
    }
}
複製程式碼

2. 測試

使用SpringBoot的專案進行測試aop,使用還是比較簡單的

@SpringBootApplication
public class Application {
    private InnerDemoBean innerDemoBean;

    public Application(InnerDemoBean innerDemoBean) {
        this.innerDemoBean = innerDemoBean;
        this.innerDemoBean();
    }

    private void innerDemoBean() {
        System.out.println("result: " + innerDemoBean.print());
    }
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}
複製程式碼

看下上面執行的輸出結果

do in around before
do before!
in innerDemoBean start!
1552219604035|e9a31f44-6a31-4485-806a-834361842ce1
in innerDemoBean over!
do in around over!
do after!
do after return: 1552219604035|e9a31f44-6a31-4485-806a-834361842ce1
result: 1552219604035|e9a31f44-6a31-4485-806a-834361842ce1
複製程式碼

從輸出結果進行反推,我們可以知道統一切面中,advice執行的先後順序如下

IMAGE

II. 同一切面,同一型別切面

正常來講,攔截一個方法時,統一型別的切面邏輯都會寫在一起,那這個case有什麼分析的必要呢?

在我們實際的使用中,同一型別的advice攔截同一個方法的可能性還是很高的,why? 因為多個advice有自己定義的攔截規則,它們之間並不相同,但可能存在交集,比如我們在上面的切面中,再加一個攔截註解的before advice

1. case設計

依然是上面的InnerDemoBean,方法上加一個自定義註解

@AnoDot
public String print() {
    try {
        System.out.println("in innerDemoBean start!");
        String rans = System.currentTimeMillis() + "|" + UUID.randomUUID();
        System.out.println(rans);
        return rans;
    } finally {
        System.out.println("in innerDemoBean over!");
    }
}
複製程式碼

然後加一個攔截註解的advice

@Before("@annotation(AnoDot)")
public void doAnoBefore(JoinPoint joinPoint) {
    System.out.println("dp AnoBefore");
}
複製程式碼

2. 測試

再次執行前面的case,然後看下輸出結果如下

In NetAspect doAround before!
do in around before
dp AnoBefore
do before!
in innerDemoBean start!
1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0
in innerDemoBean over!
do in around over!
do after!
do after return: 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0
In NetAspect doAround over! ans: 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0
result: 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0
複製程式碼

我們主要看下兩個before,發現 AnoBefore 在前面; 因此這裡的一個猜測,順序就是根據方法命名的順序來的,比如我們再加一個 doXBefore,然後我們預估輸出結果應該是

do AnoBefore > doBefore > doXBefore
複製程式碼

額外新增一個

@Before("@annotation(AnoDot)")
public void doXBefore(JoinPoint joinPoint) {
    System.out.println("dp XBefore");
}
複製程式碼

接著就是輸出結果如下,和我們預期一致

IMAGE

3. Order註解嘗試

我們知道有個Order註解可以來定義一些優先順序,那麼把這個註解放在advice方法上,有效麼?實際嘗試一下

@Order(1)
@Before(value = "point()")
public void doBefore(JoinPoint joinPoint) {
    System.out.println("do before!");
}

@Order(2)
@Before("@annotation(AnoDot)")
public void doAnoBefore(JoinPoint joinPoint) {
    System.out.println("dp AnoBefore");
}

@Order(3)
@Before("@annotation(AnoDot)")
public void doXBefore(JoinPoint joinPoint) {
    System.out.println("dp XBefore");
}
複製程式碼

如果註解有效,我們預期輸出結果如下

do Before > do AnoBefore > do XBefore
複製程式碼

然後再次執行,看下輸出結果是否和我們預期一樣

IMAGE

4. 小結

同一個切面中,相同的型別的advice,優先順序是根據方法命名來的,加@Order註解是沒有什麼鳥用的,目前也沒有搜尋到可以調整優先順序的方式

III. 不同切面,相同型別的advice

如果說上面這種case不太好理解為啥會出現的話,那麼這個可能就容易理解多了;畢竟一個切面完成一件事情,出現相同的advice就比較常見了;

比如spring mvc中,我們通常會實現的幾個切面

  • 一個before advice的切面,實現輸出請求日誌
  • 一個before advice的切面,實現安全校驗(這種其實更常見的是放在filter/intercept中)

1. case設計

現在就需要再加一個切面,依然以before advice作為case

@Aspect
@Component
public class AnotherOrderAspect {
    @Before("@annotation(AnoDot)")
    public void doBefore() {
        System.out.println("in AnotherOrderAspect before!");
    }
}
複製程式碼

2. 測試

接下來看測試輸出結果如下圖

IMAGE

發現了一個有意思的事情了,AnotherOrderAspect切面的輸出,完全在OrderAspect切面中所有的advice之前,接著我們再次嘗試使用@Order註解來試試,看下會怎樣

@Order(0)
@Component
@Aspect
public class OrderAspect {
}

@Aspect
@Order(10)
@Component
public class AnotherOrderAspect {
}
複製程式碼

如果順序有關,我們預期的輸出結果應該是

do AnoBefore > do Before > doXBefore > do AnotherOrderAspect before!
複製程式碼

實際測試輸出如下,和我們預期一致

IMAGE

3. 小結

從上面的測試來看,不同的切面,預設順序實際上是根據切面的命令來的;

  • A切面中的advice會優先B切面中同型別的advice
  • 我們可以通過 Order 註解來解決不同切面的優先順序問題,依然是值越小,優先順序越高

IV. 不同切面,不同advice順序

其實前面的case已經可以說明這個問題了,現在稍稍豐富一下AnotherOrderAspect,看下結果

1. case設計

@Aspect
@Order(10)
@Component
public class AnotherOrderAspect {

    @Before("@annotation(AnoDot)")
    public void doBefore() {
        System.out.println("in AnotherOrderAspect before!");
    }

    @After("@annotation(AnoDot)")
    public void doAfter(JoinPoint joinPoint) {
        System.out.println("do AnotherOrderAspect after!");
    }

    @AfterReturning(value = "@annotation(AnoDot)", returning = "ans")
    public void doAfterReturning(JoinPoint joinPoint, String ans) {
        System.out.println("do AnotherOrderAspect after return: " + ans);
    }

    @Around("@annotation(AnoDot)")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            System.out.println("do AnotherOrderAspect in around before");
            return joinPoint.proceed();
        } finally {
            System.out.println("do AnotherOrderAspect in around over!");
        }
    }
}
複製程式碼

2. 測試

看下執行後的輸出結果

IMAGE

假設A切面優先順序高於B切面,那麼我們執行先後順序如下

IMAGE

V. 小結

本篇內容有點多,針對前面的測試以及結果分析,給出一個小結,方便直接獲取最終的答案

1. 不同advice之間的優先順序順序

around 方法執行前程式碼  >  before > 方法執行 > around方法執行後程式碼 > after > afterReturning/@AfterThrowing
複製程式碼

2. 統一切面中相同advice

統一切面中,同型別的advice的優先順序根據方法名決定,暫未找到可以控制優先順序的使用方式

3. 不同切面優先順序

不同切面優先順序,推薦使用 @Order註解來指定,數字越低,優先順序越高

4. 不同切面advice執行順序

優先順序高的切面中的advice執行順序會呈現包圍優先順序低的advice的情況,更直觀的先後順序,推薦看第四節的順序圖,更加清晰明瞭

VI. 其他

0. 專案

1. 一灰灰Blog

一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

2. 宣告

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

3. 掃描關注

一灰灰blog

QrCode

知識星球

goals

相關文章