Spring AOP的核心原理和兩種實現(xiàn)方式全解析
在Spring框架的學(xué)習(xí)旅程中,AOP(面向切面編程)絕對是核心重點之一。它打破了傳統(tǒng)縱向編程的思維局限,通過橫向抽取機制解決了代碼冗余、耦合度高的痛點。本文將從AOP的概念引入出發(fā),層層拆解核心原理,再通過完整實戰(zhàn)案例覆蓋配置文件和注解兩種實現(xiàn)方式,幫你徹底掌握Spring AOP的應(yīng)用精髓。
一、為什么需要AOP?從登錄功能增強場景說起
要理解AOP的價值,我們先從一個常見的業(yè)務(wù)場景切入——登錄功能的增強。在基礎(chǔ)的登錄功能實現(xiàn)中,核心邏輯是校驗用戶賬號密碼的合法性,驗證通過后即可完成登錄流程。
但隨著業(yè)務(wù)迭代,我們往往需要在登錄功能之上疊加新的需求,比如「權(quán)限校驗」:不同角色的用戶登錄后能訪問的資源不同,需要在登錄后額外判斷角色權(quán)限。此時,我們有兩種實現(xiàn)思路:
- 直接修改登錄功能的源代碼,在原有邏輯中嵌入權(quán)限校驗代碼;
- 不改動原有登錄代碼,通過外部機制為登錄功能附加權(quán)限校驗?zāi)芰Α?/li>
第一種方案看似直接,卻存在諸多問題:一旦核心業(yè)務(wù)邏輯(如登錄)需要疊加多個增強功能(權(quán)限校驗、日志記錄、緩存處理等),源代碼會變得臃腫不堪,后續(xù)維護難度劇增;同時,這些增強功能在多個業(yè)務(wù)模塊中可能重復(fù)出現(xiàn),導(dǎo)致代碼冗余。
而第二種方案正是AOP的核心思想——在不修改原有業(yè)務(wù)代碼的前提下,對程序功能進行增強。這種方式能實現(xiàn)業(yè)務(wù)邏輯與增強邏輯的解耦,讓代碼結(jié)構(gòu)更清晰、可維護性更強。
二、AOP核心概念深度拆解
2.1 什么是AOP?
AOP全稱為Aspect Oriented Programming,即面向切面編程。它并非一種具體的技術(shù),而是一種編程范式,隸屬于軟件工程范疇,指導(dǎo)開發(fā)者如何更合理地組織程序結(jié)構(gòu)。
AOP的思想最早由AOP聯(lián)盟提出并制定了相關(guān)規(guī)范,Spring框架引入AOP思想時完全遵循該規(guī)范。其核心是通過預(yù)編譯或運行期動態(tài)代理技術(shù),實現(xiàn)程序功能的統(tǒng)一維護。
作為OOP(面向?qū)ο缶幊蹋┑难永m(xù),AOP彌補了OOP在橫向功能擴展上的不足。OOP通過繼承和封裝實現(xiàn)縱向的功能復(fù)用,而AOP通過橫向抽取機制,將分散在各個業(yè)務(wù)模塊中的重復(fù)代碼(如事務(wù)管理、安全檢查、日志記錄、緩存處理等)抽取出來,形成獨立的「切面」,再動態(tài)植入到需要增強的業(yè)務(wù)方法中。
一句話總結(jié)AOP的價值:隔離業(yè)務(wù)邏輯與增強邏輯,降低耦合度,提高代碼可重用性和開發(fā)效率。
2.2 AOP的核心優(yōu)勢
基于AOP的設(shè)計思想,其核心優(yōu)勢主要體現(xiàn)在三個方面:
- 減少重復(fù)代碼:將日志、權(quán)限、事務(wù)等通用增強邏輯抽取為切面,避免在多個業(yè)務(wù)模塊中重復(fù)編寫;
- 提升開發(fā)效率:開發(fā)者只需專注于核心業(yè)務(wù)邏輯的實現(xiàn),通用增強功能直接復(fù)用已有的切面,無需重復(fù)開發(fā);
- 便于維護:當(dāng)通用增強邏輯需要修改時(如日志格式調(diào)整),只需修改對應(yīng)的切面代碼,無需改動所有業(yè)務(wù)模塊,維護成本大幅降低。
2.3 AOP底層實現(xiàn)原理
Spring AOP的底層依賴兩種動態(tài)代理技術(shù)實現(xiàn),具體使用哪種技術(shù)取決于被增強的類是否實現(xiàn)接口:
1. JDK動態(tài)代理
當(dāng)被增強的類實現(xiàn)了接口時,Spring會采用JDK動態(tài)代理技術(shù)生成代理對象,核心步驟如下:
- 為被代理類的接口生成代理類的字節(jié)碼文件;
- 通過類加載器(ClassLoader)將生成的字節(jié)碼文件加載到JVM中;
- 創(chuàng)建代理類的實例對象,當(dāng)調(diào)用代理對象的方法時,會觸發(fā)增強邏輯的執(zhí)行,再調(diào)用目標(biāo)方法。
2. CGLIB動態(tài)代理
當(dāng)被增強的類沒有實現(xiàn)任何接口時,Spring會采用CGLIB動態(tài)代理技術(shù)。其核心原理是生成被代理類的子類作為代理對象,通過重寫父類的方法植入增強邏輯。由于是基于繼承實現(xiàn)的,被代理類不能是final修飾的(final類無法被繼承)。
三、Spring AOP實戰(zhàn):配置文件方式
Spring AOP的實現(xiàn)方式有兩種:配置文件方式(XML)和注解方式。我們先從配置文件方式入手,通過完整的實戰(zhàn)案例理解AOP的核心術(shù)語和實現(xiàn)流程。
3.1 先搞懂AOP的核心術(shù)語
在開始實戰(zhàn)前,必須先明確AOP的幾個核心術(shù)語,否則會對配置邏輯感到困惑:
- Joinpoint(連接點):類中可以被增強的所有方法都稱為連接點(即哪些方法有增強的可能性);
- Pointcut(切入點):從所有連接點中篩選出的、真正需要被增強的方法(即明確要對哪個方法進行增強);
- Advice(通知/增強):攔截到切入點方法后要執(zhí)行的邏輯(如權(quán)限校驗、日志記錄等),根據(jù)執(zhí)行時機不同分為前置通知、后置通知、環(huán)繞通知、最終通知、異常通知;
- Aspect(切面):切入點和通知的結(jié)合體,是AOP核心邏輯的載體(即「對哪個方法」+「做什么增強」的組合)。
3.2 實戰(zhàn)準(zhǔn)備:環(huán)境搭建
本案例基于Maven構(gòu)建項目,首先需要引入相關(guān)依賴。Spring AOP的實現(xiàn)依賴Spring核心包、AOP聯(lián)盟規(guī)范包、AspectJ相關(guān)包(用于解析切入點表達式)等,具體依賴如下:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--AOP聯(lián)盟規(guī)范包-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!--Spring Aspects包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--AspectJ織入包(解析切入點表達式)-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
</dependencies>3.3 實戰(zhàn)步驟:實現(xiàn)方法增強
步驟1:創(chuàng)建被增強的目標(biāo)類(核心業(yè)務(wù)類)
創(chuàng)建一個User類,包含add和update兩個核心業(yè)務(wù)方法,這兩個方法就是我們潛在的連接點:
// 被增強的目標(biāo)類(核心業(yè)務(wù)類)
public class User {
// 連接點/切入點
public void add(){
System.out.println("add......");
}
public void update(){
System.out.println("update......");
}
}步驟2:將目標(biāo)類交給Spring管理
在Spring配置文件(applicationContext.xml)中配置目標(biāo)類的Bean:
<bean id="user" class="com.aopImpl.User"></bean>
步驟3:創(chuàng)建切面類(增強邏輯載體)
創(chuàng)建UserProxy類作為切面類,在其中定義增強邏輯(通知)。這里先實現(xiàn)一個前置通知(目標(biāo)方法執(zhí)行前執(zhí)行的增強邏輯):
// 切面類(包含增強邏輯)
public class UserProxy {
// 前置通知:目標(biāo)方法執(zhí)行前執(zhí)行
public void before(){
System.out.println("before.............");
}
}步驟4:將切面類交給Spring管理
在配置文件中配置切面類的Bean:
<bean id="userProxy" class="com.aopImpl.UserProxy"></bean>
步驟5:配置AOP(綁定切入點和通知)
在配置文件中通過<aop:config>標(biāo)簽完成切面的配置,核心是綁定切入點(要增強的方法)和通知(增強邏輯):
<!--AOP核心配置-->
<aop:config>
<!--配置切面:關(guān)聯(lián)切面類(ref指定切面Bean的id)-->
<aop:aspect ref="userProxy">
<!--配置前置通知:指定通知方法(method)和切入點(pointcut)-->
<!--pointcut屬性:通過切入點表達式指定要增強的方法-->
<aop:before method="before" pointcut="execution(public void com.aopImpl.User.add())"/>
</aop:aspect>
</aop:config>步驟6:編寫測試類驗證結(jié)果
通過Spring容器獲取目標(biāo)類的Bean,調(diào)用add方法,觀察是否執(zhí)行增強邏輯:
public class DemoTest {
@Test
public void aopTest1(){
// 加載Spring配置文件,初始化容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 從容器中獲取User對象(實際是Spring生成的代理對象)
User user = (User) applicationContext.getBean("user");
// 調(diào)用add方法
user.add();
}
}3.4 切入點表達式詳解
在AOP配置中,切入點表達式是核心,它的作用是精準(zhǔn)定位要增強的方法。切入點表達式的完整格式如下:
execution([修飾符] [返回值類型] [類全路徑] [方法名 ( [參數(shù)] )])
各部分說明及使用規(guī)則:
- 修飾符:可選,如public、private等,可省略不寫;
- 返回值類型:必填,必須與目標(biāo)方法的返回值類型一致,可使用
*表示任意返回值類型; - 類全路徑:必填,即目標(biāo)類的包名+類名,可使用
*模糊匹配(如com.*.User表示com包下任意子包中的User類); - 方法名:必填,可使用
*表示任意方法; - 參數(shù):必填,無參數(shù)寫
(),單個任意參數(shù)寫( * ),任意個數(shù)、任意類型參數(shù)寫(..)。
常用切入點表達式示例:
- 增強com.aopImpl.User類的add方法(無參數(shù)、返回值為void):
execution(void com.aopImpl.User.add()) - 增強com.aopImpl.User類的所有方法(任意返回值、任意參數(shù)):
execution(* com.aopImpl.User.*(..)) - 增強com包下所有子包中所有類的所有方法:
execution(* com.*.*.*(..)) - 增強所有ServiceImpl結(jié)尾的類的save方法(任意返回值、任意參數(shù)):
execution(* com.*.*ServiceImpl.save(..))
3.5 五種通知類型的實現(xiàn)
除了前置通知,Spring AOP還支持五種通知類型,分別對應(yīng)不同的執(zhí)行時機。下面我們在UserProxy切面類中補充所有通知類型的實現(xiàn),并完成配置:
1. 前置通知(@Before)
目標(biāo)方法執(zhí)行前執(zhí)行,已在上面的案例中實現(xiàn),配置方式:
<aop:before method="before" pointcut="execution(* com.*.User.add(..))"/>
2. 環(huán)繞通知(@Around)
目標(biāo)方法執(zhí)行前后都執(zhí)行,需要手動調(diào)用ProceedingJoinPoint的proceed()方法觸發(fā)目標(biāo)方法執(zhí)行:
// 環(huán)繞通知
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("環(huán)繞通知-前置增強.............");
// 手動執(zhí)行目標(biāo)方法
proceedingJoinPoint.proceed();
System.out.println("環(huán)繞通知-后置增強.............");
}配置方式:
<aop:around method="around" pointcut="execution(* com.*.User.add(..))"/>
3. 最終通知(@After)
目標(biāo)方法無論執(zhí)行成功還是失敗,都會執(zhí)行(類似try-catch中的finally塊):
// 最終通知
public void after() {
System.out.println("最終通知.............");
}配置方式:
<aop:after method="after" pointcut="execution(* com.*.User.add(..))"/>
4. 后置通知(@AfterReturning)
目標(biāo)方法執(zhí)行成功后才會執(zhí)行,若目標(biāo)方法拋出異常則不執(zhí)行:
// 后置通知
public void afterReturning() {
System.out.println("后置通知.............");
}配置方式:
<aop:after-returning method="afterReturning" pointcut="execution(* com.*.User.add(..))"/>
5. 異常通知(@AfterThrowing)
目標(biāo)方法執(zhí)行失?。⊕伋霎惓#r才會執(zhí)行:
// 異常通知
public void afterThrowing() {
System.out.println("異常通知.............");
}為了驗證異常通知,需要修改目標(biāo)類的add方法,手動拋出異常:
public void add(){
// 手動制造異常
int a = 10 / 0;
System.out.println("add......");
}配置方式:
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.*.User.add(..))"/>
四、Spring AOP實戰(zhàn):注解方式(更簡潔高效)
配置文件方式的AOP雖然邏輯清晰,但配置項較多,對于復(fù)雜項目會顯得繁瑣。Spring提供了注解方式的AOP實現(xiàn),通過注解可以快速完成切面的配置,更符合現(xiàn)代開發(fā)習(xí)慣。
4.1 注解方式核心步驟
步驟1:環(huán)境準(zhǔn)備
依賴與配置文件方式一致,無需額外引入依賴。
步驟2:開啟注解掃描和AOP自動代理
在Spring配置文件中添加注解掃描和AOP自動代理的配置,開啟注解驅(qū)動的AOP功能:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--開啟注解掃描:指定要掃描的包(com.aopImpl)-->
<context:component-scan base-package="com.aopImpl"></context:component-scan>
<!--開啟AOP自動代理:讓Spring自動為切面類生成代理對象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>步驟3:使用注解標(biāo)識目標(biāo)類和切面類
通過@Component注解將目標(biāo)類和切面類交給Spring管理,通過@Aspect注解標(biāo)識切面類:
// 目標(biāo)類:通過@Component交給Spring管理
@Component
public class User {
public void add(){
System.out.println("add......");
}
}
// 切面類:@Component交給Spring管理,@Aspect標(biāo)識為切面類
@Component
@Aspect
public class UserProxy {
// 增強邏輯將在下面通過注解配置
}步驟4:使用注解配置通知和切入點
Spring提供了對應(yīng)的注解來配置五種通知類型,注解的value屬性用于指定切入點表達式:
@Component
@Aspect
public class UserProxy {
// 1. 前置通知:@Before
@Before(value = "execution(* com.*.User.add(..))")
public void before(){
System.out.println("前置通知.............");
}
// 2. 環(huán)繞通知:@Around(需手動調(diào)用proceed()執(zhí)行目標(biāo)方法)
@Around(value = "execution(* com.*.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("環(huán)繞通知-前置增強.............");
// 執(zhí)行目標(biāo)方法
proceedingJoinPoint.proceed();
System.out.println("環(huán)繞通知-后置增強.............");
}
// 3. 最終通知:@After
@After(value = "execution(* com.*.User.add(..))")
public void after() {
System.out.println("最終通知.............");
}
// 4. 異常通知:@AfterThrowing
@AfterThrowing(value = "execution(* com.*.User.add(..))")
public void afterThrowing() {
System.out.println("異常通知.............");
}
// 5. 后置通知:@AfterReturning
@AfterReturning(value = "execution(* com.*.User.add(..))")
public void afterReturning() {
System.out.println("后置通知.............");
}
}步驟5:編寫測試類驗證
測試類與配置文件方式一致,直接調(diào)用目標(biāo)方法即可:
public class DemoTest {
@Test
public void aopTest1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
user.add();
}
}運行測試方法,控制臺會按照通知的執(zhí)行順序輸出對應(yīng)的增強邏輯,說明注解方式的AOP配置生效。
五、總結(jié):兩種AOP實現(xiàn)方式對比與適用場景
通過本文的學(xué)習(xí),我們掌握了Spring AOP的核心原理和兩種實現(xiàn)方式,這里對兩種方式進行對比,幫助你選擇合適的開發(fā)方案:
- 配置文件方式:配置項清晰,易于理解和維護,適合簡單項目或?qū)ψ⒔獠皇煜さ拈_發(fā)者;缺點是配置繁瑣,復(fù)雜項目中配置文件會過于龐大。
- 注解方式:配置簡潔高效,減少了配置文件的冗余,適合復(fù)雜項目;缺點是注解分散在代碼中,對于不熟悉項目結(jié)構(gòu)的開發(fā)者來說,定位切面邏輯可能需要花費更多時間。
無論選擇哪種方式,核心都是理解AOP的「橫向抽取、解耦增強」思想。掌握AOP后,你可以輕松處理日志記錄、權(quán)限校驗、事務(wù)管理等通用功能,大幅提升代碼質(zhì)量和開發(fā)效率。
最后,建議大家多動手實踐,通過修改切入點表達式、切換通知類型等方式,深入理解AOP的執(zhí)行邏輯,真正將知識轉(zhuǎn)化為實戰(zhàn)能力。
到此這篇關(guān)于Spring AOP的核心原理和兩種實現(xiàn)方式全解析的文章就介紹到這了,更多相關(guān)Spring AOP原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud OpenFeign遠程調(diào)用傳遞請求頭信息方式
這篇文章主要介紹了SpringCloud OpenFeign遠程調(diào)用傳遞請求頭信息方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2026-03-03
java通過jni調(diào)用opencv處理圖像的方法
今天小編就為大家分享一篇java通過jni調(diào)用opencv處理圖像的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
詳解如何在SpringBoot中優(yōu)雅地重試調(diào)用第三方API
作為后端程序員,我們的日常工作就是調(diào)用一些第三方服務(wù),將數(shù)據(jù)存入數(shù)據(jù)庫,返回信息給前端。本文為大家介紹了如何在SpringBoot中優(yōu)雅地重試調(diào)用第三方API,需要的可以參考一下2022-12-12
Java項目中Service 層不直接返回Result 對象的原因分析
在Service層直接返回Result對象會導(dǎo)致業(yè)務(wù)邏輯與表現(xiàn)邏輯耦合,降低代碼清晰度和可維護性,正確的做法是讓每一層專注于自己的職責(zé),保持代碼可復(fù)用性,這篇文章給大家介紹為什么Java里面,Service 層不直接返回Result對象,感興趣的朋友跟隨小編一起看看吧2026-02-02
Spring中GET請求參數(shù)偶發(fā)性丟失問題分析及修復(fù)
本文描述了一種在SpringCloud微服務(wù)架構(gòu)下GET接口偶爾出現(xiàn)參數(shù)丟失的問題,通過源碼分析和復(fù)現(xiàn),發(fā)現(xiàn)問題是由于線程中請求對象的生命周期管理導(dǎo)致的,解決方法包括改用POST請求或更換Tomcat為Undertow中間件,需要的朋友可以參考下2025-04-04

