網(wǎng)站內(nèi)容的設(shè)計(jì)sem論壇
難
文章目錄
- 1.AOP介紹
- 1.1 面向切面編程 - Aspect Oriented Programming (AOP)
- 1.2 優(yōu)點(diǎn)
- 2.AOP的概念
- 2.1 連接點(diǎn)、切入點(diǎn)、通知、切面:
- 2.2 注解
- 2.2.1 通知類型
- 2.2.1.1 通知的優(yōu)先級排序
- 2.2.2 其他重要注解
- 2.2.3 示例代碼(四種通知)
- 3.Spring AOP配置方式
- 4.AOP的應(yīng)用
- 4.1 日志
- 4.2 異常處理
- 4.3 在spring中aop的應(yīng)用是事務(wù)
- 4.3.1 事務(wù)的傳播行為
- 4.3.2 事務(wù)的隔離級別
- 4.3.3 事務(wù)的配置方式
- 4.4 在具體項(xiàng)目中的應(yīng)用
- 5.aop底層實(shí)現(xiàn)
- 5.1 動(dòng)態(tài)代理
- 5.1.1 JDK動(dòng)態(tài)代理(實(shí)現(xiàn)和被代理類一樣的接口)
- 5.1.2 CGLIB動(dòng)態(tài)代理(創(chuàng)建目標(biāo)類的子類)
- 5.1.3 兩種代理的區(qū)別
- 5.2 后置處理器相關(guān)
- 5.2.1 后置處理器
- 5.2.2 增強(qiáng)器(封裝切面的信息)
- 5.2.3 在 getBean 時(shí)的創(chuàng)建流程
- 6.設(shè)計(jì)模式
- 6.1 代理模式
- 6.2 責(zé)任鏈模式(依次調(diào)用增強(qiáng)器的通知方法)
- 7.aop失效
- 7.1 aop失效的場景
- 7.1.1 同一個(gè)類內(nèi)部中,不同方法間的調(diào)用
- 7.1.2 多線程環(huán)境下,線程直接調(diào)用目標(biāo)對象方法
- 7.1.3 final 修飾的方法不能被重寫
- 7.1.4 private方法由于訪問權(quán)限
- 7.2 解決方法
1.AOP介紹
1.1 面向切面編程 - Aspect Oriented Programming (AOP)
AOP,作為OOP面向?qū)ο缶幊痰囊环N功能補(bǔ)充。AOP是Spring框架重要的組件,作為IOC的補(bǔ)充。
是一種思想,本質(zhì)是為了解耦。將橫切邏輯代碼抽取出去,通過簡單的配置,使用動(dòng)態(tài)代理的方式添加到目標(biāo)方法中,做到無侵入、便捷。
Spring 框架通過定義切面, 通過攔截切點(diǎn)實(shí)現(xiàn)了不同業(yè)務(wù)模塊的解耦。面向切面編程是一種編程范式,主要用于分離那些橫切多個(gè)業(yè)務(wù)模塊的關(guān)注點(diǎn),比如日志記錄、事務(wù)管理、權(quán)限驗(yàn)證等這些功能就屬于橫切關(guān)注點(diǎn)。
關(guān)注點(diǎn)是指在軟件開發(fā)過程中,系統(tǒng)中某個(gè)特定的業(yè)務(wù)功能、特性、需求或者設(shè)計(jì),是開發(fā)人員重點(diǎn)關(guān)注、考慮、實(shí)現(xiàn)的部分。
橫切關(guān)注點(diǎn)是跨越多個(gè)模塊、多個(gè)層次或者多個(gè)業(yè)務(wù)功能的關(guān)注點(diǎn)
1.2 優(yōu)點(diǎn)
橫向解決代碼重復(fù)的問題
(1)解耦橫切關(guān)注點(diǎn):將與業(yè)務(wù)邏輯不直接相關(guān)的功能,橫切的功能解耦。如日志記錄、事務(wù)管理、權(quán)限驗(yàn)證等從業(yè)務(wù)代碼中分離出來,使得業(yè)務(wù)邏輯更加清晰,易于理解和維護(hù)。
(2)代碼復(fù)用:多個(gè)業(yè)務(wù)模塊可以共用同一個(gè)切面,避免了代碼的重復(fù)編寫。
(3)靈活、可擴(kuò)展:可以方便地添加、刪除或修改切面的邏輯,而不需要對業(yè)務(wù)邏輯進(jìn)行大規(guī)模的改動(dòng)。
2.AOP的概念
我之前的一篇博文寫的
2.1 連接點(diǎn)、切入點(diǎn)、通知、切面:
(1)連接點(diǎn):可以成為切入點(diǎn)
(2)切入點(diǎn)(在哪干):需要增強(qiáng)的目標(biāo)方法。用來確定在哪些地方應(yīng)用切面的邏輯,比如可以定義在某個(gè)類的所有方法上,或者某個(gè)包下的特定方法上。
(3)通知(干什么):是定義在切面中的一段代碼,指定了在切面的切點(diǎn)位置上要執(zhí)行的具體動(dòng)作邏輯。(什么時(shí)候做、做什么)。通知是在目標(biāo)方法執(zhí)行前、后執(zhí)行的方法。通知用于在目標(biāo)方法執(zhí)行的特定時(shí)機(jī)插入額外的行為,從而實(shí)現(xiàn)對目標(biāo)對象方法的增強(qiáng)或擴(kuò)展。通知是在目標(biāo)方法執(zhí)行前、后執(zhí)行的方法。比如前置通知可以在目標(biāo)方法執(zhí)行前做一些事情,后置通知在目標(biāo)方法執(zhí)行后做一些事情,環(huán)繞通知?jiǎng)t可以在目標(biāo)方法執(zhí)行前后都進(jìn)行相應(yīng)的操作。
(4)切面=切入點(diǎn)+通知。切面是對橫切關(guān)注點(diǎn)的抽象,它把切點(diǎn)和通知組合在一起。
(5)織入:織入把切面連接到其它的應(yīng)用程序類型或者對象上,并創(chuàng)建一個(gè)被通知的對象。就是通過動(dòng)態(tài)代理對目標(biāo)對象方法進(jìn)行增強(qiáng)的過程。運(yùn)行時(shí)織入。
(6)目標(biāo)對象:被一個(gè)或者多個(gè)切面所通知的對象,也被稱作被通知對象。Spring AOP是通過運(yùn)行時(shí)代理實(shí)現(xiàn)的,所以這個(gè)對象永遠(yuǎn)是一個(gè)被代理的對象。
2.2 注解
2.2.1 通知類型
(1)@Before 前置通知(Before Advice)
定義:在目標(biāo)方法執(zhí)行之前執(zhí)行的通知。
作用:常用于進(jìn)行一些準(zhǔn)備工作,如參數(shù)校驗(yàn)、權(quán)限判斷、初始化操作等。例如,在一個(gè)用戶登錄的方法前,使用前置通知可以進(jìn)行用戶名和密碼的非空校驗(yàn),若校驗(yàn)不通過則直接返回錯(cuò)誤信息,不執(zhí)行后續(xù)的登錄邏輯。
(2)@After 后置通知(After Advice)
定義:在目標(biāo)方法執(zhí)行之后執(zhí)行的通知,無論目標(biāo)方法是否拋出異常都會(huì)執(zhí)行。
作用:可用于進(jìn)行一些資源清理、日志記錄等操作。比如在一個(gè)文件上傳的方法執(zhí)行后,使用后置通知可以關(guān)閉文件流,釋放相關(guān)資源,或者記錄文件上傳的結(jié)果信息,無論上傳是否成功都可以進(jìn)行相應(yīng)的記錄。
(3)@AfterReturning 正常返回通知(After Returning Advice)
定義:在目標(biāo)方法正常執(zhí)行完成并返回結(jié)果后執(zhí)行的通知。
作用:可以對目標(biāo)方法的返回值進(jìn)行處理或記錄。例如,在一個(gè)查詢數(shù)據(jù)庫獲取用戶信息的方法后,使用返回通知可以對查詢到的用戶信息進(jìn)行加密處理,或者記錄查詢結(jié)果以便進(jìn)行數(shù)據(jù)分析。
(4)@After-Throwing 異常通知(After Throwing Advice)
定義:在目標(biāo)方法執(zhí)行過程中拋出異常時(shí)執(zhí)行的通知。
作用:主要用于處理異常情況,如記錄異常信息、進(jìn)行異常的統(tǒng)一處理或回滾事務(wù)等。比如在一個(gè)數(shù)據(jù)更新的方法中,如果出現(xiàn)數(shù)據(jù)庫連接異常或數(shù)據(jù)沖突異常,異常通知可以捕獲并記錄這些異常,同時(shí)可以根據(jù)異常類型進(jìn)行相應(yīng)的處理,如提示用戶重新操作或回滾到之前的狀態(tài)。
(5)@Around 環(huán)繞通知(Around Advice)
定義:環(huán)繞通知可以在目標(biāo)方法執(zhí)行前后都添加自定義邏輯,它將目標(biāo)方法的執(zhí)行包裹在其中,是最強(qiáng)大也最靈活的一種通知類型。環(huán)繞通知是最常用的通知類型。
作用:可以決定目標(biāo)方法是否執(zhí)行、何時(shí)執(zhí)行以及如何執(zhí)行,還可以對目標(biāo)方法的參數(shù)和返回值進(jìn)行修改。例如,在一個(gè)遠(yuǎn)程服務(wù)調(diào)用的方法上添加環(huán)繞通知,可以在調(diào)用前進(jìn)行網(wǎng)絡(luò)連接的檢查和參數(shù)的預(yù)處理,在調(diào)用后對返回結(jié)果進(jìn)行緩存處理或錯(cuò)誤處理等 。
@Aspect
public class PerformanceAspect {@Around("execution(* com.example.service..*.*(..))")public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {long startTime = System.currentTimeMillis();Object result = pjp.proceed();long endTime = System.currentTimeMillis();System.out.println("方法執(zhí)行時(shí)間: " + (endTime - startTime) + " 毫秒");return result;}
}
2.2.1.1 通知的優(yōu)先級排序
優(yōu)先級:異常通知>后置通知>前置通知>后置返回通知。
(只要異常通知報(bào)錯(cuò),無論前置通知、后置通知、后置返回通知報(bào)錯(cuò),都將返回的是異常通知)
如果有多個(gè)通知想要在同一連接點(diǎn)運(yùn)行會(huì)發(fā)生什么?
在“進(jìn)入”連接點(diǎn)的情況下,最高優(yōu)先級的通知會(huì)先執(zhí)行(所以給定的兩個(gè)前置通知中,優(yōu)先級高的那個(gè)會(huì)先執(zhí)行)。
在“退出”連接點(diǎn)的情況下,最高優(yōu)先級的通知會(huì)最后執(zhí)行。(所以給定的兩個(gè)后置通知中, 優(yōu)先級高的那個(gè)會(huì)第二個(gè)執(zhí)行)。
2.2.2 其他重要注解
@Aspect:用來定義一個(gè)類作為切面。
@Pointcut:用于定義切點(diǎn)的位置。用于定義切點(diǎn)表達(dá)式,將切點(diǎn)表達(dá)式單獨(dú)提取出來,以便在多個(gè)通知中重復(fù)使用,提高代碼的可維護(hù)性。
@Aspect
public class LoggingAspect {@Pointcut("execution(* com.example.service..*.*(..))")public void serviceMethods() {}@Before("serviceMethods()")public void beforeServiceMethod() {System.out.println("服務(wù)方法執(zhí)行前的日志記錄");}
}
2.2.3 示例代碼(四種通知)
@Aspect
public class Logging {/** Following is the definition for a pointcut to select* all the methods available. So advice will be called* for all the methods.*/@Pointcut("execution(* com.tutorialspoint.*.*(..))")private void selectAll(){}@Before("selectAll()")public void beforeAdvice(){System.out.println("Going to setup student profile.");}@After("selectAll()")public void afterAdvice(){System.out.println("Student profile has been setup.");}@AfterReturning(pointcut = "selectAll()", returning="retVal")public void afterReturningAdvice(Object retVal){System.out.println("Returning:" + retVal.toString() );}@AfterThrowing(pointcut = "selectAll()", throwing = "ex")public void AfterThrowingAdvice(IllegalArgumentException ex){System.out.println("There has been an exception: " + ex.toString()); }
}
3.Spring AOP配置方式
支持XML模式、基于@AspectJ注解的兩種配置方式。
未詳細(xì)介紹
4.AOP的應(yīng)用
4.1 日志
以我們常見的電商系統(tǒng)為例,比如說用戶登錄、商品下單、訂單查詢等這些業(yè)務(wù)模塊,都可能需要記錄操作日志,而日志記錄這個(gè)功能并不屬于具體某個(gè)業(yè)務(wù)模塊的核心邏輯,但卻貫穿于多個(gè)業(yè)務(wù)模塊之中,這時(shí)候就適合使用 AOP 來處理。
4.2 異常處理
我的一篇博客
4.3 在spring中aop的應(yīng)用是事務(wù)
別人寫的博客
Spring AOP 為事務(wù)管理提供了一種非侵入式的實(shí)現(xiàn)方式,將事務(wù)管理這一橫切關(guān)注點(diǎn)從業(yè)務(wù)邏輯代碼中分離出來。
Spring 通過 AOP 的動(dòng)態(tài)代理機(jī)制,為被事務(wù)管理的方法創(chuàng)建代理對象。當(dāng)調(diào)用這些被代理的方法時(shí),Spring 會(huì)根據(jù)方法上的事務(wù)配置,自動(dòng)啟動(dòng)、提交或回滾事務(wù)。例如,我們常用的@Transactional注解就是基于 Spring AOP 實(shí)現(xiàn)事務(wù)管理的典型體現(xiàn) 。當(dāng)一個(gè)方法被標(biāo)記上@Transactional注解時(shí),Spring AOP 會(huì)攔截該方法的調(diào)用,并在方法執(zhí)行前開啟一個(gè)事務(wù),在方法正常執(zhí)行完成后提交事務(wù),如果方法執(zhí)行過程中拋出異常,則會(huì)自動(dòng)回滾事務(wù),確保數(shù)據(jù)的一致性。
本質(zhì)是依靠Spring框架提供的Bean生命周期相關(guān)回調(diào)接口和AOP結(jié)合完成的,簡述如下:
1.通過自動(dòng)代理創(chuàng)建器依次嘗試為每個(gè)放入容器中的bean嘗試進(jìn)行代理
2.嘗試進(jìn)行代理的過程對于事務(wù)管理來說,就是利用事務(wù)管理涉及到的增強(qiáng)器advisor,即TransactionAttributeSourceAdvisor
3.判斷當(dāng)前增強(qiáng)器是否能夠應(yīng)用在當(dāng)前bean上,怎么判斷呢? —> advisor內(nèi)部的pointCut!
4.如果能夠應(yīng)用,那么好,為當(dāng)前bean創(chuàng)建代理對象返回,并且往代理對象內(nèi)部添加一個(gè)TransactionInterceptor攔截器。
5.此時(shí)我們再從容器中獲取,拿到的就是代理對象了,當(dāng)我們調(diào)用代理對象的方法時(shí),首先要經(jīng)過代理對象內(nèi)部攔截器鏈的處理,處理完后,最終才會(huì)調(diào)用被代理對象的方法。(這里其實(shí)就是責(zé)任鏈模式的應(yīng)用)
4.3.1 事務(wù)的傳播行為
在 Spring 事務(wù)管理中,事務(wù)的傳播行為定義了多個(gè)事務(wù)方法之間相互調(diào)用時(shí),事務(wù)如何在這些方法間傳播。
例如,REQUIRED傳播行為是最常用的一種,如果當(dāng)前沒有事務(wù),就新建一個(gè)事務(wù);如果已經(jīng)存在一個(gè)事務(wù),則加入到這個(gè)事務(wù)中。這確保了在一個(gè)業(yè)務(wù)流程中,多個(gè)相關(guān)的數(shù)據(jù)庫操作可以在同一個(gè)事務(wù)的控制下,保證數(shù)據(jù)的一致性。
REQUIRES_NEW傳播行為,它總是會(huì)開啟一個(gè)新的事務(wù),而不管當(dāng)前是否已經(jīng)存在事務(wù),新事務(wù)與原有事務(wù)相互獨(dú)立。
4.3.2 事務(wù)的隔離級別
READ_UNCOMMITTED(允許讀取未提交的數(shù)據(jù),可能導(dǎo)致臟讀)、READ_COMMITTED(只能讀取已提交的數(shù)據(jù),避免臟讀,但可能出現(xiàn)不可重復(fù)讀)
REPEATABLE_READ(在同一個(gè)事務(wù)中多次讀取的數(shù)據(jù)是一致的,可防止不可重復(fù)讀,但可能出現(xiàn)幻讀)
SERIALIZABLE(最高的隔離級別,完全串行化執(zhí)行事務(wù),可避免所有并發(fā)問題,但性能較差)
4.3.3 事務(wù)的配置方式
基于注解。通過在方法或類上添加@Transactional注解,可以輕松地將該方法或類納入事務(wù)管理的范疇。注解中可以配置事務(wù)的各種屬性,如傳播行為、隔離級別、超時(shí)時(shí)間等。
此外,Spring AOP 還允許我們在配置文件或者基于 Java 的配置類中對事務(wù)進(jìn)行全局配置,設(shè)置默認(rèn)的事務(wù)屬性,如默認(rèn)的傳播行為、隔離級別等。
4.4 在具體項(xiàng)目中的應(yīng)用
在項(xiàng)目中的應(yīng)用、使用場景
5.aop底層實(shí)現(xiàn)
Java中的靜態(tài)代理和動(dòng)態(tài)代理
5.1 動(dòng)態(tài)代理
什么是動(dòng)態(tài)代理:動(dòng)態(tài)代理就是在程序運(yùn)行期,創(chuàng)建目標(biāo)對象的代理對象,并對目標(biāo)對象中的方法進(jìn)行功能性增強(qiáng)的一種技術(shù)。
Spring的代理方式有兩種,一種是 JDK動(dòng)態(tài)代理,一種是CGLIB代理。Spring默認(rèn)是JDK動(dòng)態(tài)代理。
以下兩種情況使用CGLIB代理:
①@EnableAspectJAutoProxy(proxyTargetClass = true) 強(qiáng)制要求Spring使用CGLIB代理。
②被代理的類不是接口的實(shí)現(xiàn)類。
5.1.1 JDK動(dòng)態(tài)代理(實(shí)現(xiàn)和被代理類一樣的接口)
JDK 動(dòng)態(tài)代理是利用 Java 反射機(jī)制,在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建代理類和代理對象的。它要求被代理的類必須實(shí)現(xiàn)一個(gè)或多個(gè)接口。代理類會(huì)實(shí)現(xiàn)與被代理類相同的接口,并在調(diào)用方法時(shí)通過反射機(jī)制轉(zhuǎn)發(fā)到被代理類的相應(yīng)方法上,同時(shí)可以在方法調(diào)用前后添加額外的邏輯。
使用場景:當(dāng)被代理的類已經(jīng)實(shí)現(xiàn)了接口,只需要對接口中定義的方法進(jìn)行代理增強(qiáng)時(shí)。
總結(jié):通過反射創(chuàng)建代理對象,在方法調(diào)用時(shí)通過invoke方法(反射)調(diào)用被代理類的方法,并且可以添加額外邏輯。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定義接口
interface IService {//用來調(diào)用的方法void doSomething();
}
// 實(shí)現(xiàn)接口的真實(shí)類
class ServiceImpl implements IService {//被代理對象的方法@Overridepublic void doSomething() {System.out.println("執(zhí)行具體業(yè)務(wù)邏輯");}
}
// 動(dòng)態(tài)代理處理器
class MyInvocationHandler implements InvocationHandler {private Object target;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("在方法調(diào)用前執(zhí)行額外邏輯,如日志記錄或權(quán)限驗(yàn)證");// 調(diào)用目標(biāo)對象的方法Object result = method.invoke(target, args);System.out.println("在方法調(diào)用后執(zhí)行額外邏輯,如結(jié)果處理或資源清理");return result;}
}
//流程
public class JDKDynamicProxyExample {public static void main(String[] args) {// 創(chuàng)建真實(shí)對象IService realService = new ServiceImpl();// 創(chuàng)建動(dòng)態(tài)代理對象IService proxyService = (IService) Proxy.newProxyInstance(realService.getClass().getClassLoader(),realService.getClass().getInterfaces(),new MyInvocationHandler(realService));// 調(diào)用代理對象的方法proxyService.doSomething();}
}
5.1.2 CGLIB動(dòng)態(tài)代理(創(chuàng)建目標(biāo)類的子類)
我寫的很不具體
Cglib是一個(gè)強(qiáng)大的、高性能的代碼生成包,它廣泛被許多AOP框架使用,為他們提供方法的攔截。
它不需要類實(shí)現(xiàn)接口,可以直接為普通的類創(chuàng)建代理。
它通過在運(yùn)行時(shí)動(dòng)態(tài)生成字節(jié)碼來創(chuàng)建代理類,與基于接口的 JDK 動(dòng)態(tài)代理不同。CGLIB 會(huì)在運(yùn)行時(shí)生成一個(gè)被代理類的子類,然后在這個(gè)子類中重寫被代理類的方法,通過在重寫的方法中添加一些額外的邏輯來實(shí)現(xiàn)代理的功能。
代碼中的流程:1.創(chuàng)建一個(gè)Enhancer對象,用來創(chuàng)建代理對象。2.在這個(gè)對象上,設(shè)置要被代理的類。3.設(shè)置攔截器,這個(gè)攔截器是我們寫的,攔截器里面是具體的功能:在方法執(zhí)行前后的操作,調(diào)用方法。4.用Enhancer創(chuàng)建代理對象,通過代理對象調(diào)用目標(biāo)方法。
舉個(gè)例子🌰
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 這是我們要代理的目標(biāo)類
class UserService {public void addUser(String username) {System.out.println("添加用戶: " + username);}
}
// 這是實(shí)現(xiàn)了MethodInterceptor接口的攔截器類,用來添加代理邏輯
class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 在調(diào)用目標(biāo)方法之前添加的邏輯,這里是打印日志System.out.println("開始執(zhí)行方法: " + method.getName());// 調(diào)用目標(biāo)方法,通過methodProxy的invokeSuper方法來調(diào)用,注意這里的o是代理對象Object result = methodProxy.invokeSuper(o, objects);// 在調(diào)用目標(biāo)方法之后添加的邏輯,這里也是打印日志System.out.println("方法執(zhí)行完畢: " + method.getName());return result;}
}
//調(diào)用的例子
public class CglibProxyExample {public static void main(String[] args) {// 1.創(chuàng)建Enhancer對象,它是CGLIB的核心類,用來創(chuàng)建代理對象Enhancer enhancer = new Enhancer();// 2.設(shè)置要代理的類,也就是UserService類enhancer.setSuperclass(UserService.class);// 3.設(shè)置攔截器,也就是我們剛才寫的MyMethodInterceptor類enhancer.setCallback(new MyMethodInterceptor());// 4.創(chuàng)建代理對象UserService userServiceProxy = (UserService) enhancer.create();// 通過代理對象調(diào)用目標(biāo)方法userServiceProxy.addUser("張三");}
}
5.1.3 兩種代理的區(qū)別
JDK 動(dòng)態(tài)代理要求目標(biāo)類必須實(shí)現(xiàn)接口。當(dāng)創(chuàng)建一個(gè)代理對象時(shí),JDK 會(huì)在運(yùn)行時(shí)動(dòng)態(tài)生成一個(gè)代理類,這個(gè)代理類實(shí)現(xiàn)了與目標(biāo)類相同的接口。然后通過反射機(jī)制,在代理類中調(diào)用目標(biāo)類的同名方法,并在調(diào)用前后添加切面邏輯,從而實(shí)現(xiàn)對目標(biāo)方法的增強(qiáng)。
CGLIB 通過繼承目標(biāo)類來創(chuàng)建代理類,在代理類中重寫目標(biāo)類的方法,從而實(shí)現(xiàn)對目標(biāo)方法的增強(qiáng)。
5.2 后置處理器相關(guān)
5.2.1 后置處理器
在 Spring AOP 的實(shí)現(xiàn)中,后置處理器(BeanPostProcessor)是一個(gè)關(guān)鍵的組件。它允許在 Spring 容器創(chuàng)建和初始化 Bean 的過程中,對 Bean 進(jìn)行額外的處理。
具體到 AOP,這個(gè)后置處理器承擔(dān)了重要職責(zé),它負(fù)責(zé)識別哪些 Bean 需要進(jìn)行 AOP 代理創(chuàng)建,以及如何創(chuàng)建和配置這些代理。
5.2.2 增強(qiáng)器(封裝切面的信息)
抽取切面方法為增強(qiáng)器。
增強(qiáng)器是對切面中各種通知方法以及切點(diǎn)信息等的一種封裝。它包含了要在何時(shí)(切點(diǎn))、以何種方式(通知類型)對目標(biāo)對象的哪些方法進(jìn)行增強(qiáng)的所有信息。
抽取過程:Spring 會(huì)在啟動(dòng)時(shí)掃描所有配置的切面類,解析其中的切面方法和切點(diǎn)表達(dá)式等。例如,對于一個(gè)包含了前置通知和后置通知的切面類,Spring 會(huì)將這些通知方法以及它們所對應(yīng)的切點(diǎn)信息提取出來,封裝成一個(gè)個(gè)增強(qiáng)器。
5.2.3 在 getBean 時(shí)的創(chuàng)建流程
(比如在 Spring 容器啟動(dòng)后,當(dāng)應(yīng)用程序需要獲取某個(gè) Bean 實(shí)例時(shí))Bean 創(chuàng)建時(shí),會(huì)調(diào)用 getBean 方法。此時(shí),AOP 的后置處理器就會(huì)判斷要?jiǎng)?chuàng)建的這個(gè)Bean是否為AOP目標(biāo)類、是否有切面需要對它增強(qiáng)。
如果是,就會(huì)創(chuàng)建代理類,并將增強(qiáng)器的信息保存到代理類內(nèi)部。(這樣,代理類就知道在哪些方法調(diào)用時(shí)需要應(yīng)用哪些增強(qiáng)邏輯。)
6.設(shè)計(jì)模式
6.1 代理模式
代理模式
代理模式(Proxy pattern): 為另一個(gè)對象提供一個(gè)替身或占位符,以控制對這個(gè)對象的訪問。
舉個(gè)簡單的例子:我(client)如果要買(doOperation)房,可以找中介(proxy)買房,中介直接和賣方(target)買房。中介和賣方都實(shí)現(xiàn)買賣(doOperation)的操作。中介就是代理(proxy)。
6.2 責(zé)任鏈模式(依次調(diào)用增強(qiáng)器的通知方法)
責(zé)任鏈模式的原理:責(zé)任鏈模式是一種行為設(shè)計(jì)模式,它將請求的發(fā)送者和接收者解耦,讓多個(gè)對象都有機(jī)會(huì)處理請求,形成一個(gè)鏈條,直到請求被處理為止。
在 AOP 中的應(yīng)用:當(dāng)代理類的某個(gè)方法被調(diào)用時(shí),它會(huì)根據(jù)保存的增強(qiáng)器信息(通常會(huì)使用一些數(shù)據(jù)結(jié)構(gòu)來存儲這些信息,例如,可能會(huì)使用一個(gè)列表來保存所有適用于該代理類的增強(qiáng)器,每個(gè)增強(qiáng)器在列表中都有其特定的順序和位置,以確定其執(zhí)行的先后順序。),以責(zé)任鏈的方式依次調(diào)用各個(gè)增強(qiáng)器所對應(yīng)的通知方法。例如,如果有一個(gè)前置通知增強(qiáng)器和一個(gè)后置通知增強(qiáng)器,那么在目標(biāo)方法被調(diào)用前,會(huì)先執(zhí)行前置通知增強(qiáng)器中的前置通知方法,然后調(diào)用目標(biāo)方法,最后再執(zhí)行后置通知增強(qiáng)器中的后置通知方法。
7.aop失效
7.1 aop失效的場景
7.1.1 同一個(gè)類內(nèi)部中,不同方法間的調(diào)用
當(dāng)在一個(gè)類的內(nèi)部方法中調(diào)用另一個(gè)被 AOP 增強(qiáng)的本類方法時(shí),AOP 切面可能不會(huì)生效。
這是因?yàn)?Spring AOP 默認(rèn)是基于代理的。而類內(nèi)部方法調(diào)用是直接調(diào)用目標(biāo)方法,不會(huì)通過代理對象,所以無法觸發(fā) AOP 的增強(qiáng)邏輯。
@Component
public class MyService {public void methodA() {// 直接調(diào)用本類的methodB,AOP切面不會(huì)生效methodB(); }@Transactional // 假設(shè)這是一個(gè)事務(wù)切面,正常情況下應(yīng)該開啟事務(wù)public void methodB() {// 業(yè)務(wù)邏輯代碼}
}
7.1.2 多線程環(huán)境下,線程直接調(diào)用目標(biāo)對象方法
在多線程場景中,如果線程直接調(diào)用目標(biāo)對象的方法,而不是通過代理對象調(diào)用,AOP 切面也會(huì)失效。因?yàn)槊總€(gè)線程都有自己的執(zhí)行路徑,若不通過代理,就無法觸發(fā) AOP 的攔截和增強(qiáng)機(jī)制。
通過代理對象調(diào)用方法才能aop,如果沒有代理對象直接調(diào)用方法aop失效。
public class MyService {public void doSomething() {System.out.println("執(zhí)行目標(biāo)方法");}
}
public class Main {public static void main(String[] args) {MyService target = new MyService();// 直接調(diào)用目標(biāo)對象的方法,未通過代理對象,AOP不會(huì)生效//創(chuàng)建一個(gè)新的線程對象,它定義了新線程要執(zhí)行的任務(wù)內(nèi)容。在這里,它表示新線程啟動(dòng)后要執(zhí)行的操作就是調(diào)用target對象(前面創(chuàng)建的MyService實(shí)例)的doSomething方法。new Thread(() -> target.doSomething()).start(); // 通過代理對象調(diào)用,AOP會(huì)生效MyService proxy = (MyService) ProxyFactory.getProxy(target); new Thread(() -> proxy.doSomething()).start(); }//通過一個(gè)名為ProxyFactory的工廠類的getProxy方法來為前面創(chuàng)建的目標(biāo)對象target創(chuàng)建一個(gè)代理對象。然后將創(chuàng)建好的代理對象強(qiáng)制轉(zhuǎn)換為MyService類型,并賦值給變量proxy。//new Thread后定義了新線程要執(zhí)行的任務(wù)內(nèi)容。這次是告訴新線程啟動(dòng)后要執(zhí)行的操作是調(diào)用proxy對象的doSomething方法。
}
AOP 的切面邏輯是添加在代理對象上的,只有通過代理對象調(diào)用方法,才能觸發(fā)切面的增強(qiáng)操作。
7.1.3 final 修飾的方法不能被重寫
如果一個(gè)類中的方法被聲明為 final,那么 Spring AOP 將無法對其進(jìn)行代理和增強(qiáng),因?yàn)?final 方法不能被重寫。而 AOP 的實(shí)現(xiàn)原理通常是基于動(dòng)態(tài)代理生成子類或?qū)崿F(xiàn)接口來實(shí)現(xiàn)增強(qiáng)的,這與 final 的特性相沖突。
JDK 代理是基于接口的,它主要關(guān)注的是接口中定義的方法,而接口中的方法默認(rèn)是public abstract的,不存在final修飾的情況。所以從這個(gè)角度來說,JDK 代理不會(huì)直接受到目標(biāo)類中final方法的影響,因?yàn)樗静簧婕皩δ繕?biāo)類方法的重寫,而是在實(shí)現(xiàn)接口的代理類中去調(diào)用目標(biāo)類的方法。
但是如果在接口的實(shí)現(xiàn)類中,有一些方法是final的,并且這些方法在接口中也有定義,那么實(shí)際上在代理類調(diào)用這些final方法時(shí),也無法對其進(jìn)行增強(qiáng)。
CGLIB 通過繼承目標(biāo)類來創(chuàng)建代理類。當(dāng)使用 CGLIB 創(chuàng)建代理對象時(shí),它會(huì)在運(yùn)行時(shí)動(dòng)態(tài)生成一個(gè)目標(biāo)類的子類作為代理類。這個(gè)代理類會(huì)重寫目標(biāo)類中的所有非final的方法。
@Component
public class MyFinalService {// 該方法被聲明為final,AOP切面無法生效public final void finalMethod() {// 業(yè)務(wù)邏輯代碼}
}
7.1.4 private方法由于訪問權(quán)限
private 表示私有的訪問權(quán)限,是最嚴(yán)格的訪問控制級別。被 private 修飾的成員變量和方法只能在當(dāng)前類的內(nèi)部被訪問,其他類包括子類都無法直接訪問。
在基于代理的 AOP 實(shí)現(xiàn)中,無法被外部的代理類訪問和增強(qiáng),所以 AOP 對其無效。
@Component
public class MyPrivateService {// 該方法為private,AOP切面無法生效private void privateMethod() {// 業(yè)務(wù)邏輯代碼)
}
7.2 解決方法
參考aop失效的原因,對應(yīng)解決
未寫知識:切面的實(shí)現(xiàn)過程、AOP代理的創(chuàng)建