聊聊 Spring AOP 的不為常知的“祕事”

Yin發表於2021-07-23

Spring AOP 在我們日常開發中扮演了一個非常重要的角色,對於如何使用 AOP 相信很多人已經不陌生,但其中有一些點卻容易被我們忽視,本節我們結合一些“不為常知”的問題展開討論。

同一個 AOP 類中幾個切面註解的執行順序

先給出結論:@Around [joinPoint.proceed()前] —> @Before —> @Around [joinPoint.proceed()以及之後] —> @After —> @AfterReturning(如果有異常則@AfterThrowing)

話不多說,我這們直接上程式碼:

@Aspect
public class AopClass {

    @Pointcut(value = "execution(* io.alan.*.Klass.*dong(..))")
    public void point() {

    }

    @Before(value = "point()")
    public void before() {
        System.out.println("========>begin klass dong... //2");
    }

    @Around("point()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("========>around begin klass dong //1");
        joinPoint.proceed();
        System.out.println("========>around after klass dong //3");
    }
  
    @After(value = "point()")
    public void after() {
        System.out.println("========>after klass dong... //4");
    }
  
    @AfterReturning(value = "point()")
    public void afterReturning() {
        System.out.println("========>after klass dong... //5");
    }

}

檢視應用列印的日誌,如下:

image

不同 AOP 類切面的執行順序

如果我們對同一個方法定義多個 AOP,它的執行順序是什麼樣的呢?

配置 AOP 執行順序的三種方式

方式1:實現 org.springframework.core.Ordered 介面
@Aspect
public class AopClass implements Ordered {

    @Override
    public int getOrder() {
        return 2;
    }

}
方式2:使用註解 @Order
@Aspect
@Order(2)
public class AopClass {

}
方式3:使用配置檔案
<aop:config expose-proxy="true">
	<aop:aspect ref="aopBean" order="2">  
		<aop:pointcut id="testPointcut"  expression="@annotation(xxx.xxx.xxx.annotation.xxx)"/>  
   		<aop:around pointcut-ref="testPointcut" method="doAround" />  
        </aop:aspect>  
</aop:config>

程式碼測試

下面我們演示在同一份程式碼上加上兩個 AOP,然後觀察日誌。

@Aspect
@Order(2)
public class Aop2 {

    @Pointcut(value = "execution(* io.kimmking.*.Klass.*dong(..))")
    public void point() {

    }

    @Before(value = "point()")
    public void before() {
        System.out.println("    Aop2========>before klass dong... //2");
    }

    @AfterReturning(value = "point()")
    public void afterReturning() {
        System.out.println("    Aop2========>afterReturning klass dong... //5");
    }

    @After(value = "point()")
    public void after() {
        System.out.println("    Aop2========>after klass dong... //4");
    }

    @Around("point()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("    Aop2========>around before klass dong //1");
        joinPoint.proceed();
        System.out.println("    Aop2========>around after klass dong //3");

    }

}
@Aspect
@Order(3)
public class Aop3 {

    @Pointcut(value = "execution(* io.kimmking.*.Klass.*dong(..))")
    public void point() {

    }

    @Before(value = "point()")
    public void before() {
        System.out.println("    Aop3========>before klass dong... //2");
    }

    @AfterReturning(value = "point()")
    public void afterReturning() {
        System.out.println("    Aop3========>afterReturning klass dong... //5");
    }

    @After(value = "point()")
    public void after() {
        System.out.println("    Aop3========>after klass dong... //4");
    }

    @Around("point()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("    Aop3========>around before klass dong //1");
        joinPoint.proceed();
        System.out.println("    Aop3========>around after klass dong //3");

    }

}

列印的日誌如下:

image

從日誌中我們可以看到,order 越小的 AOP 類越先執行,但還有一點需要我們注意:就是最先執行的 AOP 最後才執行結束(如上圖中的 AOP2)。

我們通過下面的這張示意圖就可以理解了。

image

小結一下:我們可以將 Spring AOP 當作一個同心圓,要執行的方法為圓心,最外層的 order 最小。從最外層按照 AOP1、AOP2 的順序依次執行 Around 方法,Before 方法。然後執行 method 方法,最後按照 AOP2、AOP1 (順序反過來了)的順序依次執行 After、AfterReturn 方法。

所以對多個AOP來說,先執行 before 的,一定後執行 after。

例如,如果我們要在同一個方法事務提交後執行自己的 AOP ,那麼可以把事務的 AOP order 設定為2,自己的 AOP order 設定為1,然後在 AfterReturn 裡邊處理自己的業務邏輯。

參考連結


END

相關文章