Spring AOP之坑:完全搞清楚advice的執行順序


原文地址 blog.csdn.net

目錄

文章目錄


AOP 的核心概念

要完全理解 Spring AOP 首先要理解 AOP 的核心概念和術語,這些術語並不是 Spring 指定的,而且很不幸,這些術語並不能直觀理解,但是,如果 Spring 使用自己的術語,那將更加令人困惑。

  • Aspect:切面,由一系列切點、增強和引入組成的模塊對象,可定義優先級,從而影響增強和引入的執行順序。事務管理(Transaction management)在 java 企業應用中就是一個很好的切面樣例。
  • Join point:接入點,程序執行期的一個點,例如方法執行、類初始化、異常處理。 在 Spring AOP 中,接入點始終表示方法執行。
  • Advice:增強,切面在特定接入點的執行動作,包括 “around,” “before” and "after" 等多種類型。包含 Spring 在內的許多 AOP 框架,通常會使用攔截器來實現增強,圍繞着接入點維護着一個攔截器鏈。
  • Pointcut:切點,用來匹配特定接入點的謂詞(表達式),增強將會與切點表達式產生關聯,並運行在任何切點匹配到的接入點上。通過切點表達式匹配接入點是 AOP 的核心,Spring 默認使用 AspectJ 的切點表達式。
  • Introduction:引入,為某個 type 聲明額外的方法和字段。Spring AOP 允許你引入任何接口以及它的默認實現到被增強對象上。
  • Target object:目標對象,被一個或多個切面增強的對象。也叫作被增強對象。既然 Spring AOP 使用運行時代理(runtime proxies),那么目標對象就總是代理對象。
  • AOP proxy:AOP 代理,為了實現切面功能一個對象會被 AOP 框架創建出來。在 Spring 框架中 AOP 代理的默認方式是:有接口,就使用基於接口的 JDK 動態代理,否則使用基於類的 CGLIB 動態代理。但是我們可以通過設置proxy-target-class="true",完全使用 CGLIB 動態代理。
  • Weaving:織入,將一個或多個切面與類或對象鏈接在一起創建一個被增強對象。織入能發生在編譯時 (compile time )(使用 AspectJ 編譯器),加載時(load time),或運行時(runtime) 。Spring AOP 默認就是運行時織入,可以通過枚舉AdviceMode來設置。

模擬 aspect advice 的執行過程

在這里我們不再展示測試代碼,而是通過簡單的代碼來模擬 aspect advice 的執行過程

盡管 Spring AOP 是通過動態代理來實現的,但是我們可以繞過代理,直接模擬出它的執行過程,示例代碼:

package doubt;
public class AspectAdviceInvokeProcess {
	public static void main(String[] args){
		try {
		    //正常執行
			AspectInvokeProcess(false);
			System.out.println("=====分割線=====");
			//異常執行
			AspectInvokeProcess(true);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * 切面執行過程
	 * @param isException
	 * @throws Exception
	 */
	public static void AspectInvokeProcess(boolean isException) throws Exception{
		try {
			try {
				aroundAdvice(isException);
			} finally {
				afterAdvice();
			}
		    afterReturningAdvice();
		    return;
		} catch (Exception e) {
			afterThrowingAdvice(e);
			throw e;
			return;
		}	
	}
	
	/**
	 * 環繞增強
	 * @param isException
	 * @throws Exception
	 */
	private static void aroundAdvice(boolean isException) throws Exception {
		System.out.println("around before advice");
		try {
			JoinPoint_Proceed(isException);
		} finally {
			System.out.println("around after advice");
		}
	}
	
	/**
	 * 編織后的接入點執行過程
	 * @param isException
	 */
	public static void JoinPoint_Proceed(boolean isException){
		beforeAdvice();
		targetMethod(isException);
	}
	
	/**
	 * 前置增強
	 */
	private static void beforeAdvice() {
		System.out.println("before advice");
	}

	/**
	 * 目標方法
	 * @param isException
	 */
	private static void targetMethod(boolean isException) {
		System.out.println("target method 執行");
		if(isException)
			throw new RuntimeException("異常發生");
	}
	
	/**
	 * 后置增強
	 */
	private static void afterAdvice() {
		System.out.println("after advice");
	}

	/**
	 * 正常返回增強
	 */
	private static void afterReturningAdvice() {
		System.out.println("afterReturning");
	}

	/**
	 * 異常返回增強
	 * @param e
	 * @throws Exception
	 */
	private static void afterThrowingAdvice(Exception e) throws Exception {
		System.out.println("afterThrowing:"+e.getMessage());
	}
}

同一 aspect,不同 advice 的執行順序

上述代碼的執行結果,直接體現了同一apsect中不同advice的執行順序,結果如下:

around before advice
before advice
target method 執行
around after advice
after advice
afterReturning
===============分割線==============
around before advice
before advice
target method 執行
around after advice
after advice
afterThrowing:異常發生
java.lang.RuntimeException: 異常發生

得出結論:


不同 aspect,advice 的執行順序

詳情可見,《Spring 官方文檔》
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-ataspectj-advice-ordering

Spring AOP 通過指定aspect的優先級,來控制不同aspect,advice的執行順序,有兩種方式:

  • Aspect 類添加注解:org.springframework.core.annotation.Order,使用注解value屬性指定優先級。

  • Aspect 類實現接口:org.springframework.core.Ordered,實現 Ordered 接口的 getOrder() 方法。

其中,數值越低,表明優先級越高,@Order 默認為最低優先級,即最大數值:

/**
	 * Useful constant for the lowest precedence value.
	 * @see java.lang.Integer#MAX_VALUE
	 */
	int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

最終,不同aspect,advice的執行順序

  • 入操作(Around(接入點執行前)、Before),優先級越高,越先執行;
  • 一個切面的入操作執行完,才輪到下一切面,所有切面入操作執行完,才開始執行接入點;
  • 出操作(Around(接入點執行后)、After、AfterReturning、AfterThrowing),優先級越低,越先執行。
  • 一個切面的出操作執行完,才輪到下一切面,直到返回到調用點;

如下圖所示:

先入后出,后入先出


同一 aspect,相同 advice 的執行順序

同一aspect,相同advice的執行順序並不能直接確定,而且 @Orderadvice方法上也無效,但是有如下兩種變通方式:

  • 將兩個 advice 合並為一個 advice,那么執行順序就可以通過代碼控制了
  • 將兩個 advice 分別抽離到各自的 aspect 內,然后為 aspect 指定執行順序

Transactional Aspect 的優先級

Spring 事務管理(Transaction Management),也是基於 Spring AOP。

在 Spring AOP 的使用中,有時我們必須明確自定義 aspect 的優先級低於或高於事務切面(Transaction Aspect),所以我們需要知道:

  • 事務切面優先級:默認為最低優先級
LOWEST_PRECEDENCE = Integer.MAX_VALUE
  • 事務的增強類型:Around advice,其實不難理解,進入方法開啟事務,退出方法提交或回滾,所以需要環繞增強。
public abstract aspect AbstractTransactionAspect extends TransactionAspectSupport implements DisposableBean {

   protected AbstractTransactionAspect(TransactionAttributeSource tas) {
   	setTransactionAttributeSource(tas);
   }

   @SuppressAjWarnings("adviceDidNotMatch")
   Object around(final Object txObject): transactionalMethodExecution(txObject) {
   	MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
   	// Adapt to TransactionAspectSupport's invokeWithinTransaction...
   	try {
   		return invokeWithinTransaction(methodSignature.getMethod(), txObject.getClass(), new InvocationCallback() {
   			public Object proceedWithInvocation() throws Throwable {
   				return proceed(txObject);
   			}
   		});
   	}
   	catch (RuntimeException ex) {
   		throw ex;
   	}
   	catch (Error err) {
   		throw err;
   	}
   	catch (Throwable thr) {
   		Rethrower.rethrow(thr);
   		throw new IllegalStateException("Should never get here", thr);
   	}
   }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM