Java AOP面向切面編程的概念和實現(xiàn)方式
一、AOP 是什么?
AOP(Aspect-Oriented Programming),即面向切面編程。它是一種編程范式,與我們所熟悉的OOP(面向?qū)ο缶幊蹋┦?strong>互補的關(guān)系,而不是替代。
- OOP 的局限:OOP的核心是對象和類,它擅長將功能進行縱向的模塊化劃分(例如,
UserService類負責(zé)用戶相關(guān)所有邏輯)。但對于一些需要橫向地散布 across 多個模塊的功能,OOP就顯得力不從心。- 例如:日志記錄、性能監(jiān)控、事務(wù)管理、安全校驗。這些邏輯幾乎需要出現(xiàn)在每一個業(yè)務(wù)方法中,但它們本質(zhì)上又不屬于核心業(yè)務(wù)邏輯。
- AOP 的解決方案:AOP將這些散布在各處的橫切關(guān)注點(Cross-Cutting Concerns)從核心業(yè)務(wù)邏輯中分離出來,封裝成一個獨立的可重用的模塊,稱為切面(Aspect)。然后,在程序運行的合適時機,AOP會自動地將這些切面代碼“織入”到需要它們的方法中。
看下面的例子:

面向?qū)ο缶幊蘋PP完成了穿衣、吃飯、洗碗等這些功能,但從另一個維度去控制這些功能,比如“查看穿衣服、吃飯的時間(性能監(jiān)控)”、“保證吃飯、洗碗連在一起完成(事務(wù)管理)”、“記錄洗碗、打掃消耗的用水量(日志記錄)”這些就是面向切面編程AOP
- 沒有AOP,就得在每個功能上添加相應(yīng)控制,比如在穿衣服寫一遍記錄時間的代碼,然后再再吃飯?zhí)帉懸槐橛涗洉r間的代碼。這顯然不合理。AOP讓核心業(yè)務(wù)(穿衣、吃飯)和通用功能(記錄時間)得以解耦。
二、AOP 的核心概念與實現(xiàn)方式
核心概念
- Aspect(切面):封裝橫切關(guān)注點的模塊。它是一個類,上面標(biāo)注了
@Aspect注解。例如:LoggingAspect(日志切面)、TransactionAspect(事務(wù)切面)。 - Advice(通知):切面中的具體方法。它定義了“做什么”以及“何時做”。
- 何時做:
@Before(方法前)、@After(方法后)、@AfterReturning(成功返回后)、@AfterThrowing(拋出異常后)、@Around(環(huán)繞,最強大)。 - Pointcut(切點):一個表達式,定義了“在何處做”,即匹配哪些類的哪些方法需要被增強。它決定了Advice的應(yīng)用位置。
- Join Point(連接點):程序執(zhí)行過程中能夠插入切面的一個點,例如方法調(diào)用、異常拋出等。在Spring AOP中,連接點總是代表一個方法的執(zhí)行。
- Weaving(織入):將切面代碼應(yīng)用到目標(biāo)對象,從而創(chuàng)建代理對象的過程。Spring AOP在運行時通過動態(tài)代理完成織入。
實現(xiàn)方式
Spring AOP 的底層就是基于我們之前討論過的動態(tài)代理:
- 如果目標(biāo)對象實現(xiàn)了接口,默認使用 JDK 動態(tài)代理。
- 如果目標(biāo)對象沒有實現(xiàn)接口,則使用 CGLIB 庫生成子類進行代理。
三、Spring AOP 的關(guān)鍵注解
| 注解 | 說明 |
|---|---|
@Aspect | 聲明一個類是切面。 |
@Pointcut | 聲明一個切點表達式,可被通知方法引用,避免重復(fù)書寫。 |
@Before | 前置通知:在目標(biāo)方法執(zhí)行之前執(zhí)行。 |
@AfterReturning | 返回通知:在目標(biāo)方法成功執(zhí)行并返回后執(zhí)行。 |
@AfterThrowing | 異常通知:在目標(biāo)方法拋出異常后執(zhí)行。 |
@After | 后置通知:在目標(biāo)方法執(zhí)行之后執(zhí)行(無論成功還是異常,類似于finally)。 |
@Around | 環(huán)繞通知:最強大的通知類型,可以手動控制目標(biāo)方法的執(zhí)行時機,可以在方法執(zhí)行前后添加自定義行為。 |
四、實際場景與代碼示例
場景一:記錄洗碗和打掃的用水量
1. 業(yè)務(wù)類(核心方法)
WashDish.java - 洗碗類
@Component
public class WashDish {
private int water;
public int fillWater(int amount) {
this.water += amount;
System.out.println("洗碗接水: " + amount + "升");
return amount;
}
public void wash() {
System.out.println("使用" + water + "升水洗碗");
water = 0;
}
}CleanRoom.java - 打掃類
@Component
public class CleanRoom {
private int water;
public int fillWater(int amount) {
this.water += amount;
System.out.println("打掃接水: " + amount + "升");
return amount;
}
public void clean() {
System.out.println("使用" + water + "升水打掃房間");
water = 0;
}
}2. 切面類(AOP實現(xiàn))
WaterUsageLoggerAspect.java - 用水量記錄切面
/**
* 切面類
*/
@Aspect
@Component
public class WaterUsageLoggerAspect {
private Map<String, Integer> waterUsageRecords = new HashMap<>();
/**
* 切入點:規(guī)定哪些類被控制
*/
@Pointcut("execution(* WashDish.fillWater(..)) || execution(* CleanRoom.fillWater(..))")
public void waterUsagePointcut() {}
/**
* 通知
*/
@AfterReturning(pointcut = "waterUsagePointcut()", returning = "waterAmount")
public void logWaterUsage(int waterAmount) {
String activity = "家務(wù)活動";
waterUsageRecords.put(activity, waterUsageRecords.getOrDefault(activity, 0) + waterAmount);
System.out.println("記錄用水: " + waterAmount + "升,當(dāng)前總用水量: " + waterUsageRecords.get(activity) + "升");
}
public int getTotalWaterUsage() {
return waterUsageRecords.values().stream().mapToInt(Integer::intValue).sum();
}
}3. 配置類(Spring配置)
AppConfig.java - 應(yīng)用配置
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example.aop")
public class AppConfig {
}4. 測試類(演示代碼)
HomeChoresTest.java - 家務(wù)測試
public class HomeChoresTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
WashDish washDish = context.getBean(WashDish.class);
CleanRoom cleanRoom = context.getBean(CleanRoom.class);
WaterUsageLoggerAspect waterLogger = context.getBean(WaterUsageLoggerAspect.class);
System.out.println("=== 開始家務(wù)活動 ===");
// 洗碗活動
System.out.println("\n--- 洗碗 ---");
washDish.fillWater(10);
washDish.wash();
// 打掃活動
System.out.println("\n--- 打掃 ---");
cleanRoom.fillWater(15);
cleanRoom.clean();
System.out.println("\n=== 家務(wù)完成 ===");
System.out.println("總用水量: " + waterLogger.getTotalWaterUsage() + "升");
context.close();
}
}5. 預(yù)期輸出
運行測試類后,預(yù)期輸出如下:
=== 開始家務(wù)活動 ===
--- 洗碗 ---
洗碗接水: 10升
記錄用水: 10升,當(dāng)前總用水量: 10升
使用10升水洗碗
--- 打掃 ---
打掃接水: 15升
記錄用水: 15升,當(dāng)前總用水量: 25升
使用15升水打掃房間
=== 家務(wù)完成 === 總用水量: 25升
代碼說明
- 業(yè)務(wù)類:包含核心的家務(wù)邏輯(洗碗和打掃),每個類都有一個
fillWater方法用于接水。 - 切面類:
- 使用
@Aspect注解標(biāo)記為切面 - 使用
@Pointcut定義切入點,匹配所有接水操作 - 使用
@AfterReturning后置通知記錄用水量 - 維護用水記錄并提供查詢接口
- 使用
- 配置類:啟用Spring AOP自動代理和組件掃描
- 測試類:演示如何使用AOP記錄家務(wù)活動的用水量
五、AOP 的常見應(yīng)用場景
- 日志記錄:如上例,記錄方法入?yún)ⅰ⒊鰠?、?zhí)行耗時,用于調(diào)試和監(jiān)控。
- 事務(wù)管理:這是最經(jīng)典的應(yīng)用! Spring的
@Transactional注解就是基于AOP實現(xiàn)的。它在方法開始時開啟事務(wù),在方法成功執(zhí)行后提交事務(wù),在拋出異常時回滾事務(wù)。 - 權(quán)限校驗和安全控制:在方法執(zhí)行前,判斷當(dāng)前用戶是否有權(quán)限執(zhí)行此操作。例如使用
@PreAuthorize注解。 - 性能監(jiān)控:統(tǒng)計方法的執(zhí)行時間,上報給監(jiān)控系統(tǒng),用于發(fā)現(xiàn)性能瓶頸。
- 異常處理與統(tǒng)一返回:捕獲服務(wù)層拋出的異常,將其轉(zhuǎn)換為友好的錯誤信息格式返回給前端。
- 緩存:在方法執(zhí)行前檢查緩存中是否有數(shù)據(jù),如果有則直接返回,否則執(zhí)行方法并將結(jié)果放入緩存。
總結(jié)
- AOP是什么:一種將橫切關(guān)注點(日志、事務(wù)等)與核心業(yè)務(wù)邏輯分離的技術(shù)。
- 如何實現(xiàn):Spring AOP通過動態(tài)代理在運行時將切面“織入”到目標(biāo)方法中。
- 核心注解:
@Aspect,@Pointcut,@Around,@Before,@After等。 - 為何需要:實現(xiàn)解耦、提高代碼的可復(fù)用性和可維護性,讓開發(fā)者能更專注于核心業(yè)務(wù)邏輯。
通過AOP,我們可以以一種非常優(yōu)雅和非侵入式的方式,為應(yīng)用程序添加強大的功能。
到此這篇關(guān)于java:AOP面向切面編程的文章就介紹到這了,更多相關(guān)java aop切面內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中float類型的范圍及其與十六進制的轉(zhuǎn)換例子
這篇文章主要介紹了Java中float類型的范圍及其與十六進制的轉(zhuǎn)換例子,是Java入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-10-10
Java實現(xiàn)求子數(shù)組和的最大值算法示例
這篇文章主要介紹了Java實現(xiàn)求子數(shù)組和的最大值算法,涉及Java數(shù)組遍歷、判斷、運算等相關(guān)操作技巧,需要的朋友可以參考下2018-02-02
springcloud使用profile實現(xiàn)多環(huán)境配置方式
這篇文章主要介紹了springcloud使用profile實現(xiàn)多環(huán)境配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Java Hibernate使用SessionFactory創(chuàng)建Session案例詳解
這篇文章主要介紹了Java Hibernate使用SessionFactory創(chuàng)建Session案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08
spring基礎(chǔ)概念A(yù)OP與動態(tài)代理理解
這篇文章主要為大家詳細介紹了spring基礎(chǔ)概念A(yù)OP與動態(tài)代理,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10

