Spring AOP 的實現原理
一、AOP的基本概念
將橫切關注點(日志、事務、權限)從業務邏輯中分離出來,提高代碼的可維護性。
下面將解釋,AOP專屬名詞,切面、連接點、切點、通知、目標對象、代理對象:
- 切面:切面是封裝橫切關注點的模塊,比如日志記錄。 @Aspect 修飾類,如 LoggingAspect
- 連接點:連接點就是作用的實際方法
- 切點:@Pointcut("execution(* org.example.tao.service.impl.UserServiceImpl.getAllUsers(..))")
- 通知:@Before @AfterReturning @AfterThrowing @After @Around
- 目標對象:原始業務對象
- 代理對象:Spring 啟動時動態生成的代理對象
二、Spring AOP
2.1 實現原理
Spring AOP 是基于動態代理實現的,具體有倆種代理方式:
- JDK 動態代理(需要有接口)
- CGLIB 動態代理
2.2 優劣勢
優點:
- 解耦:將橫切關注點與業務邏輯分離,提高代碼的可維護性。
- 靈活:通過切點表達式可以靈活地定義攔截規則。
- 非侵入式:無需修改目標類代碼,即可實現功能增強。
缺點:
- 性能開銷:動態代理會引入一定的性能開銷。
- 局限性:只能攔截Spring管理的Bean,且無法攔截非public方法。
2.3 工作流程
- 定義切面:
使用@Aspect注解定義切面類。
在切面類中定義通知方法,并使用@Before、@After等注解指定通知類型。 - 定義切點:
使用@Pointcut注解定義切點表達式,指定哪些方法會被攔截。 - 生成代理對象:
Spring容器在初始化時,根據切面和切點生成代理對象。
如果目標類實現了接口,使用JDK動態代理;否則使用CGLIB動態代理。 - 執行通知:
當目標方法被調用時,代理對象會根據切點表達式判斷是否需要攔截。
如果需要攔截,則按照通知類型(如前置通知、后置通知)執行通知邏輯。
三、展示一下
3.1 引入依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
3.2 定義切面類
@Aspect // 封裝切面關注點的模塊,比如日志記錄
@Component
public class LoggingAspect {
// 切點,通過表達式匹配連接點 // todo: 實際替換為自己想要切面的方法。
@Pointcut("execution(* org.example.tao.service.impl.UserServiceImpl.getAllUsers(..))")
public void loggingPointCut() {}
// 前置通知,作用于連接點
@Before("loggingPointCut()")
public void logBefore() {
System.out.println("方法執行前的日志記錄");
}
@AfterReturning(pointcut = "loggingPointCut()", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("方法返回后的日志記錄,返回值:" + result);
}
@AfterThrowing(pointcut = "loggingPointCut()", throwing = "exception")
public void logAfterThrowing(Exception exception) {
System.out.println("方法拋出異常后的日志記錄,異常:" + exception.getMessage());
}
@After("loggingPointCut()")
public void logAfter() {
System.out.println("方法執行后的日志記錄");
}
@Around("loggingPointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Signature signature = joinPoint.getSignature();
System.out.println("環繞日志開始,方法名:" + signature.getName());
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 執行目標方法
long elapsedTime = System.currentTimeMillis() - start;
System.out.println("環繞日志結束,耗時:" + elapsedTime + "ms");
return result;
}
}
3.3 驗證

啟動程序后,調用連接點方法,便會執行方法。
本文來自博客園,作者:帥氣的濤啊,轉載請注明原文鏈接:http://www.rzrgm.cn/handsometaoa/p/18782188

浙公網安備 33010602011771號