代碼的隱形守護者:Spring AOP 是如何做到的?
如何使用AOP
用 “打針” 場景一次性記住 AOP 核心概念
想象你去醫院打針的過程:
目標對象(Target)
就是 “你”(被打針的人),核心業務是 “生病看病”。
連接點(Join Point)
你身體上所有能打針的地方(胳膊、屁股等所有可能的位置)。
用打針場景重新理解「切點(Pointcut)」
還是以打針為例,但更貼近@Pointcut注解和表達式的作用:
你去醫院打針時,護士不會隨便找個地方扎,而是要按「規則」精準定位 —— 這個「規則」就是切點。
比如醫院規定:
「只給兒科的發燒患者,在胳膊三角肌打退燒針」
這里的「兒科患者 + 發燒 + 胳膊三角肌 + 退燒針」就是一套「切點規則」,對應 AOP 中@Pointcut的表達式邏輯。
對應到代碼中的切點定義
@Pointcut注解就像醫院的「打針規則手冊」,而execution 表達式就是手冊里的具體條款:
// 例:攔截所有Controller中public方法的切點
@Pointcut("execution(public * cn.soboys.controller.*.*(..))")
public void controllerPointcut() {}
這個表達式的含義,對應到打針場景就是:
-
execution:「在打針動作執行時生效」(方法執行時觸發); -
public :「不管是哪種針(肌肉針 / 靜脈針),只要是公開給患者的操作」(任意返回值的 public 方法);
-
cn.soboys.controller.*:「只針對兒科診室里的所有病床」(指定包下的所有類); -
*(..):「不管患者年齡、體重如何,只要是打針操作都算」(任意方法名,任意參數)。

切點的核心作用:精準篩選,可重用
- 精準篩選:就像醫院用「兒科 + 發燒 + 退燒針」篩出需要特殊處理的患者,
execution表達式通過「返回值 + 類名 + 方法名 + 參數」精準定位要攔截的方法(比如只攔截UserController里帶@PostMapping的方法)。 - 可重用:定義一次
@Pointcut("xxx"),后續所有通知(前置 / 后置等)都能直接引用這個切點,就像醫院把「兒科退燒針規則」寫在手冊里,所有護士都能按同一套規則執行,不用每次重新定義。
通知(Advice)
打針的具體動作:
- 前置通知:打針前 “用酒精棉消毒”;
- 后置通知:打完后 “貼創可貼”;
- 環繞通知:“按住皮膚→扎針→推藥→拔針”(全程包圍核心動作);
- 異常通知:如果 “打針時出血了”,醫生會額外 “止血”。
切面(Aspect)
醫生的 “打針流程手冊”,里面寫清了 “在胳膊三角?。ㄇ悬c)打針,步驟是消毒→扎針→貼創可貼(通知)”——切點 + 通知 = 切面。
- 織入(Weaving):醫生按照手冊給你打針的過程(把流程 “套” 到你身上)。
一句話總結
- 連接點:所有可能被 “切” 的地方(能打針的所有位置);
- 切點:實際被 “切” 的地方(選好的打針位置);
- 通知:“切” 的時候做什么(消毒、扎針等動作);
- 切面:規定 “在哪里切 + 切了做什么”(完整的打針方案)。
AOP 最適合的場景
記?。?strong>所有 “重復出現在多個業務里,但又不是核心業務” 的功能,都該用 AOP。
比如:
- 日志:每個接口都要記錄請求,但日志不是業務本身;
- 權限:每個敏感操作都要驗權限,驗權限不是業務目的;
- 事務:訂單、支付等操作都要事務控制,控制事務不是業務核心;
- 監控:統計每個方法的執行時間,統計不是業務邏輯。
就像打針時,“消毒、貼創可貼” 不是看病的核心(核心是給藥),但每個打針流程都需要 —— 這就是 AOP 要干的事。
案例說明
本案例的主要目標是通過兩個不同的業務類(Calculator 和 UserService),展示 AOP 在實際項目中的應用。具體來說,使用 AOP 實現以下功能:
- 對
Calculator類的所有方法進行日志記錄,包括方法調用前、調用后、正常返回和拋出異常時的日志。 - 對
UserService類的方法進行日志記錄、數據脫敏和權限檢查。
導入依賴
<!--aop 切面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定義業務邏輯類
UserService 類
import lombok.Data;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public User getUserById(int id) {
// 模擬返回用戶信息
return new User(id, "Alice", "alice@example.com");
}
public void createUser(User user) {
// 模擬創建用戶
System.out.println("創建用戶: " + user);
}
public void updateUser(User user) {
// 模擬更新用戶
System.out.println("更新用戶: " + user);
}
public void deleteUser(int id) {
// 模擬刪除用戶
System.out.println("刪除用戶,ID: " + id);
}
}
@Data
class User {
private int id;
private String name;
private String email;
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
意義:UserService 類模擬了一個用戶服務,包含了獲取用戶信息、創建用戶、更新用戶和刪除用戶的方法。通過對這個類的方法進行 AOP 處理,可以展示 AOP 在更復雜業務場景下的應用,如數據脫敏和權限檢查。
定義切面
@Aspect
@Component
public class UserAspect {
// 定義切點,攔截UserService類的所有方法
@Pointcut("execution(* org.example.aopdemo.UserService.*(..))")
public void allMethods() {}
// 定義切點,攔截UserService類中createUser和updateUser方法
@Pointcut("execution(* org.example.aopdemo.UserService.createUser(..)) || execution(* org.example.aopdemo.UserService.updateUser(..))")
public void userOperations() {}
// 定義切點,攔截UserService類中getUserById方法
@Pointcut("execution(* org.example.aopdemo.UserService.getUserById(..))")
public void getUserOperations() {}
// 前置通知:在方法執行之前執行
@Before("allMethods()")
public void beforeAdvice(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("前置通知:方法 " + methodName + " 被調用,參數為 " + Arrays.toString(joinPoint.getArgs()));
}
// 后置通知:在方法執行之后執行,無論是否發生異常
@After("allMethods()")
public void afterAdvice(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("后置通知:方法 " + methodName + " 已執行完畢。");
}
// 返回通知:在方法正常返回后執行
@AfterReturning(pointcut = "getUserOperations()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("返回通知:方法 " + methodName + " 返回結果為 " + result);
// 數據脫敏處理
if (result instanceof User) {
User user = (User) result;
System.out.println("數據脫敏處理:用戶信息已脫敏,返回的用戶ID為 " + user.getId());
}
}
// 異常通知:在方法拋出異常后執行
@AfterThrowing(pointcut = "allMethods()", throwing = "exception")
public void afterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("異常通知:方法 " + methodName + " 拋出異常:" + exception.getMessage());
}
// 環繞通知:對createUser和updateUser方法進行權限檢查
@Around("userOperations()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("環繞通知:方法 " + methodName + " 開始執行,進行權限檢查...");
// 模擬權限檢查
if (!hasPermission()) {
throw new SecurityException("沒有權限執行此操作!");
}
Object result = joinPoint.proceed(); // 繼續執行原方法
System.out.println("環繞通知:方法 " + methodName + " 執行完成。");
return result;
}
private boolean hasPermission() {
// 模擬權限檢查邏輯
return true; // 假設當前用戶有權限
}
}
在 AOP(面向切面編程)中,JoinPoint 是一個非常重要的概念,JoinPoint joinPoint 中的 JoinPoint 是一個接口,它代表程序執行過程中的某個特定位置,例如方法調用、異常拋出等。下面詳細解釋 JoinPoint 的含義、作用以及在提供的代碼中的使用方式。
JoinPoint 的含義
JoinPoint 是 AOP 中的一個抽象概念,它表示程序執行過程中可以插入切面代碼的點。在 Spring AOP 里,JoinPoint 通常代表方法的執行,因為 Spring AOP 主要基于方法攔截來實現。
JoinPoint 的作用
JoinPoint 提供了一系列方法,用于獲取與當前連接點相關的信息,比如:
- 獲取方法簽名:可以獲取被調用方法的名稱、參數類型等信息。
- 獲取方法參數:可以獲取傳遞給被調用方法的實際參數。
- 獲取目標對象:可以獲取被調用方法所在的對象實例。
以這個為例子說明:
public void beforeAdvice(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getMethod().getName();
System.out.println("前置通知:方法 " + methodName + " 被調用,參數為 " + Arrays.toString(joinPoint.getArgs()));
}
joinPoint.getSignature():getSignature()是JoinPoint接口的一個方法,用于獲取當前連接點的簽名信息,返回的是一個Signature類型的對象。(MethodSignature):由于我們處理的是方法調用,所以將Signature對象強制轉換為MethodSignature類型,以便獲取方法的具體信息。
測試執行
@SpringBootApplication
public class AopDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(AopDemoApplication.class, args);
UserService userService = context.getBean(UserService.class);
System.out.println("\n測試getUserById方法:");
User user = userService.getUserById(1);
System.out.println("\n測試createUser方法:");
userService.createUser(new User(2, "Bob", "bob@example.com"));
System.out.println("\n測試updateUser方法:");
userService.updateUser(new User(2, "Bob Updated", "bob-updated@example.com"));
System.out.println("\n測試deleteUser方法:");
userService.deleteUser(2);
}
}

這里寫的只不過是測試方法,在項目中應該是寫到config文件中。直接配置,注入到springboot容器中。

浙公網安備 33010602011771號