| 通知 | 註解 | 執行時機 | 典型用途 | 參數 |
| 前置 | @Before | 目標方法**執行前** | 權限校驗、參數檢查、日誌記錄 | JoinPoint |
| 後置 | @After | 目標方法**執行後**(無論正常/異常) | 資源清理、釋放鎖、計時結束 | JoinPoint |
| 返回 | @AfterReturning | 目標方法**正常返回後** | 結果加工、緩存寫入、審計日誌 | JoinPoint + returning |
| 異常 | @AfterThrowing | 目標方法**拋出異常後** | 異常統一處理、錯誤上報、補償邏輯 | JoinPoint + throwing |
| 環繞 | @Around | **完全控制**目標方法執行(前/後/異常/不執行) | 事務管理、性能監控、重試、緩存攔截 | ProceedingJoinPoint |
| 引入 | @DeclareParents | 為目標類動態添加父類/接口 | 極少用,混入功能 | -- |
連接點 API 實戰:獲取方法簽名、參數、目標對象
@Aspect @Component
public class LoggingAspect {
private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
@Pointcut("execution(* com.example.service..*(..))")
public void serviceLayer() {}
@Before("serviceLayer()")
public void logBefore(JoinPoint jp) {
Signature sig = jp.getSignature(); // 方法簽名
String methodName = sig.getName();
String className = sig.getDeclaringTypeName();
Object[] args = jp.getArgs(); // 實際參數
Object target = jp.getTarget(); // 目標對象實例
log.info("▶ {}.{} args={}", className, methodName, Arrays.toString(args));
}
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfter(JoinPoint jp, Object result) {
log.info("■ {}.{} result={}", jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName(), result);
}
@Around("serviceLayer()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
try {
return pjp.proceed(); // **必須調用**,否則目標方法不執行
} finally {
long cost = (System.nanoTime() - start) / 1_000_000;
log.info("⏱ {}.{} took {} ms", pjp.getSignature().getDeclaringTypeName(), pjp.getSignature().getName(), cost);
}
}
}
切點表達式語法速查
execution(modifiers? return_type package.Class.method(args))
// 常用模式
execution(* com.example.service.*.*(..)) // service 包所有類所有方法
execution(* com.example.service.UserService.*(..)) // 指定類所有方法
execution(* com.example.service.*.save*(..)) // 以 save 開頭的方法
execution(* *(..)) // 所有方法(慎用)
execution(!private * *(..)) // 非私有方法
execution(public * *(@Transactional ..)) // 參數帶註解的方法
切點表達式復用與組合
@Aspect @Component
public class CommonPointcuts {
@Pointcut("execution(* com.example.service..*(..))")
public void inServiceLayer() {}
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactional() {}
@Pointcut("inServiceLayer() && transactional()")
public void transactionalInService() {}
}
// 其他切面引用
@Aspect @Component
public class TxAspect {
@Around("com.example.aop.CommonPointcuts.transactionalInService()")
public Object manageTx(ProceedingJoinPoint pjp) throws Throwable { ... }
}
多切面執行順序控制:@Order
- 數值**越小優先級越高**(先織入)。
- 環繞通知:@Order(1) 的 @Around **最外層**包裹 @Order(2) 的 @Around。
- 前置通知:@Order(1) 的 @Before **先於** @Order(2) 執行。
- 後置/返回/異常通知:@Order(1) **後於** @Order(2) 執行(棧式展開)。
@Aspect @Component @Order(1) // 最外層事務
public class TransactionAspect { ... }
@Aspect @Component @Order(2) // 內層日誌
public class LoggingAspect { ... }
JoinPoint vs ProceedingJoinPoint 選擇指南
JoinPoint:用於 @Before、@After、@AfterReturning、@AfterThrowing,**只讀**連接點信息。
ProceedingJoinPoint:僅用於 @Around,**必須調用 proceed()** 繼續執行目標方法,可決定是否執行、執行幾次、捕獲異常、修改參數/返回值。
常見坑點
| 坑點 | 現象 | 解決 |
| @Around 忘記 proceed() | 目標方法不執行,返回 null | 必寫 return pjp.proceed(); 或手動返回值 |
| proceed() 拋異常未處理 | 異常傳播導致事務不回滾 | try-finally 包裹、或拋出 Throwable 讓 Spring 統一處理 |
| 切點表達式過寬 | 誤攔截內部方法、Getter/Setter、性能損耗 | 精確到包/類/方法前綴、排除 getter/setter |
| 同類內部方法調用 | AOP 失效(繞過代理) | 注入自身 AopContext.currentProxy() 或拆分類 |
- 上一篇文章: Spring MVC + MyBatis + MySQL 實戰項目環境搭建:STS、JDK 8、Maven 多模塊最佳實踐
- 下一篇文章: Spring Boot + MyBatis 多数据源实战:AOP 切面自动路由实现读写分离与多数据源切换
Copyright ©
程式碼之家 All Rights Reserved