核心定義
@After
是 Spring AOP 中的另一種通知(Advice)類型,通常被稱為“后置通知”或“最終通知”。
它的核心作用是:
無論目標方法是正常執行完成,還是在執行過程中拋出了異常,@After
通知中的代碼 總是 會在目標方法執行之后被執行。
最經典的類比就是 Java 中的 try...catch...finally
語句塊里的 finally
部分。@After
的行為和 finally
塊的行為幾乎一模一樣。
@After
通知的執行流程
為了更好地理解,我們來看兩種情況下的執行順序:
情況一:目標方法成功執行
@Before
通知執行。- 目標方法 (
targetMethod()
) 執行并正常返回。 @After
通知執行。- (如果定義了)
@AfterReturning
通知執行。
情況二:目標方法拋出異常
@Before
通知執行。- 目標方法 (
targetMethod()
) 執行,中途拋出異常。 @After
通知執行。- (如果定義了)
@AfterThrowing
通知執行。 - 異常繼續向上層調用棧拋出。
一個非常關鍵的點是:@After
通知本身無法訪問目標方法的返回值(因為它可能根本沒有返回值,比如拋異常時),也無法捕獲或處理從目標方法中拋出的異常。它只是一個保證“最后一定會被執行”的鉤子。
@After
通知能做什么?(主要應用場景)
后置通知非常適合執行那些必須進行的“清理”或“收尾”工作,無論業務邏輯成功與否。
-
資源釋放 (Resource Cleanup)
- 這是
@After
最重要、最常見的用途。類似于finally
塊。 - 示例:釋放文件句柄、關閉網絡連接、關閉數據庫連接池中的連接等。確保即使業務代碼出錯,關鍵資源也不會被泄露。
- 這是
-
上下文清理 (Context Cleanup)
- 如果在
@Before
通知中向ThreadLocal
存放了數據,那么在@After
通知中將其remove()
是一個最佳實踐。這可以防止在線程池環境中發生內存泄漏或數據錯亂。
- 如果在
-
最終日志記錄 (Final Auditing)
- 記錄一個操作的結束。
- 示例:“方法
updateProduct
執行完畢。” 這個日志不關心成功或失敗,只記錄“結束”這個事實。
-
性能監控 (Performance Monitoring)
- 可以在
@Before
中記錄一個開始時間,然后在@After
中記錄結束時間,并計算總耗時。 - 示例:
@Before
:long startTime = System.currentTimeMillis();
(存入ThreadLocal
)@After
:long endTime = System.currentTimeMillis(); long duration = endTime - startTime; log.info("方法耗時: {} ms", duration);
- 可以在
與 @AfterReturning
和 @AfterThrowing
的區別
這是新手很容易混淆的地方,理解它們的區別至關重要:
通知類型 | 執行時機 | 能否訪問返回值? | 能否訪問異常? | 主要用途 |
---|---|---|---|---|
@After (最終通知) | 總是在目標方法后執行(無論成功或失敗) | 不能 | 不能 | 資源清理、最終日志 |
@AfterReturning (返回通知) | 僅在目標方法成功執行后執行 | 可以 | 不適用 | 基于返回結果的附加操作 |
@AfterThrowing (異常通知) | 僅在目標方法拋出異常后執行 | 不適用 | 可以 | 異常日志記錄、告警通知 |
簡單來說:
- 想總是執行清理?用
@After
。 - 想在成功后根據返回值做點事?用
@AfterReturning
。 - 想在失敗后專門處理異常?用
@AfterThrowing
。
代碼示例
我們擴展之前的例子,增加一個刪除方法(可能會失敗),并為所有方法添加 @After
通知。
1. 業務服務類 (目標對象)
package com.example.service;import org.springframework.stereotype.Service;@Service
public class UserService {// 成功執行的例子public String findUserById(Long id) {System.out.println("--- 核心業務邏輯:正在根據 ID 查詢用戶... ---");return "User" + id;}// 拋出異常的例子public void deleteUser(Long id) {System.out.println("--- 核心業務邏輯:正在嘗試刪除用戶... ---");if (id <= 0) {throw new IllegalArgumentException("用戶ID無效,刪除失敗!");}System.out.println("用戶 " + id + " 已被成功刪除。");}
}
2. 切面類 (Aspect) 中定義 @After
通知
package com.example.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;@Aspect
@Component
public class LoggingAspect {@Pointcut("execution(public * com.example.service.*.*(..))")public void serviceLayerPointcut() {}// 前置通知@Before("serviceLayerPointcut()")public void logBefore(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("==================================================");System.out.printf("[AOP 前置通知]: 方法 [%s] 即將執行... 參數: %s%n", methodName, Arrays.toString(args));}// 后置通知 (最終通知)@After("serviceLayerPointcut()")public void logAfter(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();System.out.printf("[AOP 后置通知]: 方法 [%s] 執行完畢。執行清理工作...%n", methodName);System.out.println("--------------------------------------------------\n");}
}
3. 運行代碼并觀察輸出
調用成功的方法 userService.findUserById(101L)
:
==================================================
[AOP 前置通知]: 方法 [findUserById] 即將執行... 參數: [101]
--- 核心業務邏輯:正在根據 ID 查詢用戶... ---
[AOP 后置通知]: 方法 [findUserById] 執行完畢。執行清理工作...
--------------------------------------------------
@After
在方法成功后執行了。
調用失敗的方法 userService.deleteUser(0L)
(需要用 try-catch 捕獲異常):
try {userService.deleteUser(0L);
} catch (Exception e) {System.err.println("在調用方捕獲到異常: " + e.getMessage());
}
輸出:
==================================================
[AOP 前置通知]: 方法 [deleteUser] 即將執行... 參數: [0]
--- 核心業務邏輯:正在嘗試刪除用戶... ---
[AOP 后置通知]: 方法 [deleteUser] 執行完畢。執行清理工作...
--------------------------------------------------在調用方捕獲到異常: 用戶ID無效,刪除失敗!
即使
deleteUser
拋出了異常,@After
通知 (logAfter
方法) 依然被執行了,完美地展示了其finally
的特性。
總結
特性 | 描述 |
---|---|
執行時機 | 無論成功或失敗,總是在目標方法執行之后執行。 |
核心用途 | 資源釋放、上下文清理、最終日志記錄等收尾工作。 |
行為類似 | Java 的 finally 語句塊。 |
關鍵限制 | 無法訪問目標方法的返回值,也無法捕獲或修改異常。 |
關鍵參數 | 可以注入 JoinPoint 對象,獲取方法元數據。 |