?<前文回顧>
<今日更新>
一、開篇整活兒
今兒個咱嘮嘮 Spring Boot 里頭的 AOP(面向切面編程)。這玩意兒吧,說大不大,說小不小,整好了是錦上添花,整不好就是火上澆油。你要是剛入門,那可得悠著點兒,別一上來就整得自己“翻車”了。
二、AOP 是啥玩意兒?
AOP 是 Spring 里頭的一個高級特性,用來在不修改原有代碼的情況下,給程序動態添加功能。比如說,你可以用 AOP 來記錄日志、監控性能、處理異常啥的。Spring Boot 里頭默認就集成了這玩意兒,用起來賊方便。
1. AOP 的核心概念
AOP 里頭有幾個核心概念:切面(Aspect)、連接點(Join Point)、通知(Advice)、切點(Pointcut)。
- 切面:就是你要添加的功能,比如說日志記錄、性能監控啥的。
- 連接點:就是程序執行過程中的某個點,比如說方法調用、異常拋出啥的。
- 通知:就是切面在連接點執行的動作,比如說在方法調用前記錄日志。
- 切點:就是用來匹配連接點的表達式,比如說匹配某個包下的所有方法。
2. AOP 的通知類型
AOP 里頭有五種通知類型:
- 前置通知(Before):在連接點之前執行。
- 后置通知(After):在連接點之后執行,不管連接點是否拋出異常。
- 返回通知(AfterReturning):在連接點正常返回后執行。
- 異常通知(AfterThrowing):在連接點拋出異常后執行。
- 環繞通知(Around):在連接點前后都執行,可以控制連接點的執行。
三、用 AOP 實現日志記錄
日志記錄是 AOP 的經典應用場景。你可以用 AOP 來記錄方法的調用信息,方便以后排查問題。
1. 定義切面
首先,你得定義一個切面,用?@Aspect?注解標記。
Java Code |
@Aspect @Component public class LoggingAspect { ????private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); } |
這段代碼里頭,LoggingAspect?是一個切面,@Aspect?注解標記了這個類,@Component?注解讓 Spring 管理這個類。
2. 定義切點
然后,你得定義一個切點,用?@Pointcut?注解標記。
Java Code |
@Aspect @Component public class LoggingAspect { ????private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); ????@Pointcut("execution(* com.example.demo.service.*.*(..))") ????public void serviceMethods() {} } |
這段代碼里頭,serviceMethods?是一個切點,execution(* com.example.demo.service.*.*(..))?表示匹配?com.example.demo.service?包下的所有方法。
3. 定義通知
最后,你得定義通知,用?@Before、@After、@AfterReturning、@AfterThrowing?或?@Around?注解標記。
Java Code |
@Aspect @Component public class LoggingAspect { ????private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); ????@Pointcut("execution(* com.example.demo.service.*.*(..))") ????public void serviceMethods() {} ????@Before("serviceMethods()") ????public void logMethodCall(JoinPoint joinPoint) { ????????logger.info("調用方法:{}", joinPoint.getSignature().getName()); ????} } |
這段代碼里頭,logMethodCall?是一個前置通知,@Before?注解標記了這個方法,JoinPoint?參數用來獲取連接點的信息。
四、用 AOP 實現性能監控
性能監控是 AOP 的另一個經典應用場景。你可以用 AOP 來記錄方法的執行時間,方便以后優化性能。
1. 定義切面
首先,你得定義一個切面,用?@Aspect?注解標記。
Java Code |
@Aspect @Component public class PerformanceAspect { ????private static final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class); } |
這段代碼里頭,PerformanceAspect?是一個切面,@Aspect?注解標記了這個類,@Component?注解讓 Spring 管理這個類。
2. 定義切點
然后,你得定義一個切點,用?@Pointcut?注解標記。
Java Code |
@Aspect @Component public class PerformanceAspect { ????private static final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class); ????@Pointcut("execution(* com.example.demo.service.*.*(..))") ????public void serviceMethods() {} } |
這段代碼里頭,serviceMethods?是一個切點,execution(* com.example.demo.service.*.*(..))?表示匹配?com.example.demo.service?包下的所有方法。
3. 定義通知
最后,你得定義通知,用?@Around?注解標記。
Java Code |
@Aspect @Component public class PerformanceAspect { ????private static final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class); ????@Pointcut("execution(* com.example.demo.service.*.*(..))") ????public void serviceMethods() {} ????@Around("serviceMethods()") ????public Object measureMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { ????????long startTime = System.currentTimeMillis(); ????????Object result = joinPoint.proceed(); ????????long endTime = System.currentTimeMillis(); ????????logger.info("方法 {} 執行時間:{} 毫秒", joinPoint.getSignature().getName(), endTime - startTime); ????????return result; ????} } |
這段代碼里頭,measureMethodExecutionTime?是一個環繞通知,@Around?注解標記了這個方法,ProceedingJoinPoint?參數用來控制連接點的執行。
五、AOP 的坑點
1. 切點表達式寫錯了
AOP 里頭,切點表達式寫錯了,那通知就不起作用了。你要是寫錯了,那可得好好檢查檢查。
Java Code |
@Pointcut("execution(* com.example.demo.service.*.*(..))") // 寫錯了 public void serviceMethods() {} |
這段代碼里頭,execution?寫錯了,應該是?execution。
當然,寫不好表達式,可以問AI啊。
2. 通知順序不對
AOP 里頭,通知順序不對,那結果就不對了。你要是順序不對,那可得好好調整調整。
Java Code |
@Before("serviceMethods()") public void logMethodCall(JoinPoint joinPoint) { ????logger.info("調用方法:{}", joinPoint.getSignature().getName()); } @Around("serviceMethods()") public Object measureMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { ????long startTime = System.currentTimeMillis(); ????Object result = joinPoint.proceed(); ????long endTime = System.currentTimeMillis(); ????logger.info("方法 {} 執行時間:{} 毫秒", joinPoint.getSignature().getName(), endTime - startTime); ????return result; } |
這段代碼里頭,logMethodCall?和?measureMethodExecutionTime?的順序很重要。
3. 切面沒被 Spring 管理
AOP 里頭,切面沒被 Spring 管理,那通知就不起作用了。你要是沒被管理,那可得好好檢查檢查。
Java Code |
@Aspect // 沒加 @Component public class LoggingAspect { ????private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); ????@Pointcut("execution(* com.example.demo.service.*.*(..))") ????public void serviceMethods() {} ????@Before("serviceMethods()") ????public void logMethodCall(JoinPoint joinPoint) { ????????logger.info("調用方法:{}", joinPoint.getSignature().getName()); ????} } |
這段代碼里頭,LoggingAspect?沒加?@Component?注解,Spring 不會管理這個類。
六、額外再說一點
對于大多數 Spring Boot 項目,如果你只是簡單地使用 AOP 來實現日志記錄、事務管理等功能,并且已經引入了?spring-boot-starter-aop?依賴,那么通常不需要顯式地使用?@EnableAspectJAutoProxy?注解。Spring Boot 會自動為你處理相關的配置。
然而,如果你有特殊的需求,比如自定義代理創建策略或確保 AOP 支持被啟用,那么你可以考慮顯式地使用?@EnableAspectJAutoProxy?注解。
專有名詞解釋
- AOP:面向切面編程,一種編程范式,用來在不修改原有代碼的情況下,給程序動態添加功能。
- 切面:AOP 里頭的一個概念,表示你要添加的功能。
- 連接點:AOP 里頭的一個概念,表示程序執行過程中的某個點。
- 通知:AOP 里頭的一個概念,表示切面在連接點執行的動作。
- 切點:AOP 里頭的一個概念,表示用來匹配連接點的表達式。
- 前置通知:AOP 里頭的一種通知類型,在連接點之前執行。
- 后置通知:AOP 里頭的一種通知類型,在連接點之后執行。
- 返回通知:AOP 里頭的一種通知類型,在連接點正常返回后執行。
- 異常通知:AOP 里頭的一種通知類型,在連接點拋出異常后執行。
- 環繞通知:AOP 里頭的一種通知類型,在連接點前后都執行。
- JoinPoint:AOP 里頭的一個接口,用來獲取連接點的信息。
- ProceedingJoinPoint:AOP 里頭的一個接口,用來控制連接點的執行。