1. 程式人生 > 程式設計 >Spring AOP之坑:完全搞清楚advice的執行順序

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

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官方檔案》 docs.spring.io/spring/docs…

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);
   	}
   }
}
複製程式碼
  • 如何修改事務切面的優先順序: 在開啟事務時,通過設定 @EnableTransactionManagement<tx:annotation-driven/> 中的, order 屬性來修改事務切面的優先順序。 詳情可見,《Spring官方檔案》docs.spring.io/spring/docs…