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

面向?qū)ο缶幊蘋PP完成了穿衣、吃飯、洗碗等這些功能,但從另一個(gè)維度去控制這些功能,比如“查看穿衣服、吃飯的時(shí)間(性能監(jiān)控)”、“保證吃飯、洗碗連在一起完成(事務(wù)管理)”、“記錄洗碗、打掃消耗的用水量(日志記錄)”這些就是面向切面編程AOP
- 沒有AOP,就得在每個(gè)功能上添加相應(yīng)控制,比如在穿衣服寫一遍記錄時(shí)間的代碼,然后再再吃飯?zhí)帉懸槐橛涗洉r(shí)間的代碼。這顯然不合理。AOP讓核心業(yè)務(wù)(穿衣、吃飯)和通用功能(記錄時(shí)間)得以解耦。
二、AOP 的核心概念與實(shí)現(xiàn)方式
核心概念
- Aspect(切面):封裝橫切關(guān)注點(diǎn)的模塊。它是一個(gè)類,上面標(biāo)注了
@Aspect注解。例如:LoggingAspect(日志切面)、TransactionAspect(事務(wù)切面)。 - Advice(通知):切面中的具體方法。它定義了“做什么”以及“何時(shí)做”。
- 何時(shí)做:
@Before(方法前)、@After(方法后)、@AfterReturning(成功返回后)、@AfterThrowing(拋出異常后)、@Around(環(huán)繞,最強(qiáng)大)。 - Pointcut(切點(diǎn)):一個(gè)表達(dá)式,定義了“在何處做”,即匹配哪些類的哪些方法需要被增強(qiáng)。它決定了Advice的應(yīng)用位置。
- Join Point(連接點(diǎn)):程序執(zhí)行過程中能夠插入切面的一個(gè)點(diǎn),例如方法調(diào)用、異常拋出等。在Spring AOP中,連接點(diǎn)總是代表一個(gè)方法的執(zhí)行。
- Weaving(織入):將切面代碼應(yīng)用到目標(biāo)對(duì)象,從而創(chuàng)建代理對(duì)象的過程。Spring AOP在運(yùn)行時(shí)通過動(dòng)態(tài)代理完成織入。
實(shí)現(xiàn)方式
Spring AOP 的底層就是基于我們之前討論過的動(dòng)態(tài)代理:
- 如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)使用 JDK 動(dòng)態(tài)代理。
- 如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口,則使用 CGLIB 庫生成子類進(jìn)行代理。
三、Spring AOP 的關(guān)鍵注解
| 注解 | 說明 |
|---|---|
@Aspect | 聲明一個(gè)類是切面。 |
@Pointcut | 聲明一個(gè)切點(diǎn)表達(dá)式,可被通知方法引用,避免重復(fù)書寫。 |
@Before | 前置通知:在目標(biāo)方法執(zhí)行之前執(zhí)行。 |
@AfterReturning | 返回通知:在目標(biāo)方法成功執(zhí)行并返回后執(zhí)行。 |
@AfterThrowing | 異常通知:在目標(biāo)方法拋出異常后執(zhí)行。 |
@After | 后置通知:在目標(biāo)方法執(zhí)行之后執(zhí)行(無論成功還是異常,類似于finally)。 |
@Around | 環(huán)繞通知:最強(qiáng)大的通知類型,可以手動(dòng)控制目標(biāo)方法的執(zhí)行時(shí)機(jī),可以在方法執(zhí)行前后添加自定義行為。 |
四、實(shí)際場景與代碼示例
場景一:記錄洗碗和打掃的用水量
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實(shí)現(xiàn))
WaterUsageLoggerAspect.java - 用水量記錄切面
/**
* 切面類
*/
@Aspect
@Component
public class WaterUsageLoggerAspect {
private Map<String, Integer> waterUsageRecords = new HashMap<>();
/**
* 切入點(diǎn):規(guī)定哪些類被控制
*/
@Pointcut("execution(* WashDish.fillWater(..)) || execution(* CleanRoom.fillWater(..))")
public void waterUsagePointcut() {}
/**
* 通知
*/
@AfterReturning(pointcut = "waterUsagePointcut()", returning = "waterAmount")
public void logWaterUsage(int waterAmount) {
String activity = "家務(wù)活動(dòng)";
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. 測(cè)試類(演示代碼)
HomeChoresTest.java - 家務(wù)測(cè)試
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ù)活動(dòng) ===");
// 洗碗活動(dòng)
System.out.println("\n--- 洗碗 ---");
washDish.fillWater(10);
washDish.wash();
// 打掃活動(dòng)
System.out.println("\n--- 打掃 ---");
cleanRoom.fillWater(15);
cleanRoom.clean();
System.out.println("\n=== 家務(wù)完成 ===");
System.out.println("總用水量: " + waterLogger.getTotalWaterUsage() + "升");
context.close();
}
}5. 預(yù)期輸出
運(yùn)行測(cè)試類后,預(yù)期輸出如下:
=== 開始家務(wù)活動(dòng) ===
--- 洗碗 ---
洗碗接水: 10升
記錄用水: 10升,當(dāng)前總用水量: 10升
使用10升水洗碗
--- 打掃 ---
打掃接水: 15升
記錄用水: 15升,當(dāng)前總用水量: 25升
使用15升水打掃房間
=== 家務(wù)完成 === 總用水量: 25升
代碼說明
- 業(yè)務(wù)類:包含核心的家務(wù)邏輯(洗碗和打掃),每個(gè)類都有一個(gè)
fillWater方法用于接水。 - 切面類:
- 使用
@Aspect注解標(biāo)記為切面 - 使用
@Pointcut定義切入點(diǎn),匹配所有接水操作 - 使用
@AfterReturning后置通知記錄用水量 - 維護(hù)用水記錄并提供查詢接口
- 使用
- 配置類:啟用Spring AOP自動(dòng)代理和組件掃描
- 測(cè)試類:演示如何使用AOP記錄家務(wù)活動(dòng)的用水量
五、AOP 的常見應(yīng)用場景
- 日志記錄:如上例,記錄方法入?yún)?、出參、?zhí)行耗時(shí),用于調(diào)試和監(jiān)控。
- 事務(wù)管理:這是最經(jīng)典的應(yīng)用! Spring的
@Transactional注解就是基于AOP實(shí)現(xiàn)的。它在方法開始時(shí)開啟事務(wù),在方法成功執(zhí)行后提交事務(wù),在拋出異常時(shí)回滾事務(wù)。 - 權(quán)限校驗(yàn)和安全控制:在方法執(zhí)行前,判斷當(dāng)前用戶是否有權(quán)限執(zhí)行此操作。例如使用
@PreAuthorize注解。 - 性能監(jiān)控:統(tǒng)計(jì)方法的執(zhí)行時(shí)間,上報(bào)給監(jiān)控系統(tǒng),用于發(fā)現(xiàn)性能瓶頸。
- 異常處理與統(tǒng)一返回:捕獲服務(wù)層拋出的異常,將其轉(zhuǎn)換為友好的錯(cuò)誤信息格式返回給前端。
- 緩存:在方法執(zhí)行前檢查緩存中是否有數(shù)據(jù),如果有則直接返回,否則執(zhí)行方法并將結(jié)果放入緩存。
總結(jié)
- AOP是什么:一種將橫切關(guān)注點(diǎn)(日志、事務(wù)等)與核心業(yè)務(wù)邏輯分離的技術(shù)。
- 如何實(shí)現(xiàn):Spring AOP通過動(dòng)態(tài)代理在運(yùn)行時(shí)將切面“織入”到目標(biāo)方法中。
- 核心注解:
@Aspect,@Pointcut,@Around,@Before,@After等。 - 為何需要:實(shí)現(xiàn)解耦、提高代碼的可復(fù)用性和可維護(hù)性,讓開發(fā)者能更專注于核心業(yè)務(wù)邏輯。
通過AOP,我們可以以一種非常優(yōu)雅和非侵入式的方式,為應(yīng)用程序添加強(qiáng)大的功能。
到此這篇關(guān)于java:AOP面向切面編程的文章就介紹到這了,更多相關(guān)java aop切面內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot整合Shiro實(shí)現(xiàn)登錄與權(quán)限校驗(yàn)詳細(xì)解讀
本文給大家介紹Springboot整合Shiro的基本使用,Apache?Shiro是Java的一個(gè)安全框架,Shiro本身無法知道所持有令牌的用戶是否合法,我們將整合Shiro實(shí)現(xiàn)登錄與權(quán)限的驗(yàn)證2022-04-04
Spring攔截器HandlerInterceptor接口代碼解析
這篇文章主要介紹了Spring攔截器HandlerInterceptor接口代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下2017-12-12
Mybatis基礎(chǔ)概念與高級(jí)應(yīng)用小結(jié)
這篇文章主要介紹了Mybatis基礎(chǔ)回顧與高級(jí)應(yīng)用,本文內(nèi)容有點(diǎn)小長,希望大家耐心閱讀,此文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Java使用Socket判斷某服務(wù)能否連通代碼實(shí)例
這篇文章主要介紹了Java使用Socket判斷某服務(wù)能否連通代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
JWT + 攔截器實(shí)現(xiàn)無狀態(tài)登錄系統(tǒng)
JWT(JSON Web Token)提供了一種無狀態(tài)的解決方案:用戶登錄后,服務(wù)器返回一個(gè) Token,后續(xù)請(qǐng)求攜帶該 Token 即可完成身份驗(yàn)證,無需服務(wù)器存儲(chǔ)會(huì)話信息,本文將結(jié)合 Spring Boot 攔截器,手把手實(shí)現(xiàn)一個(gè)完整的JWT無狀態(tài)登錄系統(tǒng),感興趣的朋友一起看看吧2025-08-08
基于java ssm springboot實(shí)現(xiàn)選課推薦交流平臺(tái)系統(tǒng)
這篇文章主要介紹了選課推薦交流平臺(tái)系統(tǒng)是基于java ssm springboot來的實(shí)現(xiàn)的,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08

