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"
-
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的執行順序
並不能直接確定,而且 @Order 在advice
方法上也無效,但是有如下兩種變通方式:
- 將兩個 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…