死磕Spring之AOP篇 - Spring AOP常見(jiàn)面試題
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | 月圓吖
來(lái)源 | urlify.cn/ueQ3Qj
什么是 AOP?
官方文檔:
AspectJ:Aspect-oriented programming is a way of modularizing crosscutting concerns much like object-oriented programming is a way of modularizing common concerns.
Spring:Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and objects. (Such concerns are often termed “crosscutting” concerns in AOP literature.)
AOP(Aspect-oriented Programming)面向切面編程,是一種開(kāi)發(fā)理念,是 OOP 面向?qū)ο缶幊痰难a(bǔ)充。我們知道,Java 就是一門(mén)面向?qū)ο缶幊痰恼Z(yǔ)言,在 OOP 中最小的單元就是“Class 對(duì)象”,但是在 AOP 中最小的單元是“切面”。一個(gè)“切面”可以包含很多種類型和對(duì)象,對(duì)它們進(jìn)行模塊化管理,例如事務(wù)管理。
為什么要引入 AOP?
Java OOP 存在哪些局限性?
靜態(tài)化語(yǔ)言:類結(jié)構(gòu)一旦定義,不容易被修改
侵入性擴(kuò)展:通過(guò)繼承或組合組織新的類結(jié)構(gòu)
通過(guò) AOP 我們可以把一些非業(yè)務(wù)邏輯的代碼(比如安全檢查、監(jiān)控等代碼)從業(yè)務(wù)中抽取出來(lái),以非入侵的方式與原方法進(jìn)行協(xié)同。這樣可以使得原方法更專注于業(yè)務(wù)邏輯,代碼接口會(huì)更加清晰,便于維護(hù)。
簡(jiǎn)述 AOP 的使用場(chǎng)景?
日志場(chǎng)景
診斷上下文,如:log4j 或 logback 中的 _x0008_MDC
輔助信息,如:方法執(zhí)行時(shí)間
統(tǒng)計(jì)場(chǎng)景
方法調(diào)用次數(shù)
執(zhí)行異常次數(shù)
數(shù)據(jù)抽樣
數(shù)值累加
安防場(chǎng)景
熔斷,如:Netflix Hystrix
限流和降級(jí):如:Alibaba Sentinel
認(rèn)證和授權(quán),如:Spring Security
監(jiān)控,如:JMX
性能場(chǎng)景
緩存,如 Spring Cache
超時(shí)控制
可以說(shuō)在我們的日常開(kāi)發(fā)環(huán)境中都是離不開(kāi) AOP 的。
簡(jiǎn)述 AOP 中幾個(gè)比較重要的概念
在 AOP 中有以下幾個(gè)概念:
AspectJ:切面,只是一個(gè)概念,沒(méi)有具體的接口或類與之對(duì)應(yīng),是 Join point,Advice 和 Pointcut 的一個(gè)統(tǒng)稱。
Join point:連接點(diǎn),指程序執(zhí)行過(guò)程中的一個(gè)點(diǎn),例如方法調(diào)用、異常處理等。在 Spring AOP 中,僅支持方法級(jí)別的連接點(diǎn)。
Advice:通知,即我們定義的一個(gè)切面中的橫切邏輯,有“around”,“before”和“after”三種類型。在很多的 AOP 實(shí)現(xiàn)框架中,Advice 通常作為一個(gè)攔截器,也可以包含許多個(gè)攔截器作為一條鏈路圍繞著 Join point 進(jìn)行處理。
Pointcut:切點(diǎn),用于匹配連接點(diǎn),一個(gè) AspectJ 中包含哪些 Join point 需要由 Pointcut 進(jìn)行篩選。
Introduction:引介,讓一個(gè)切面可以聲明被通知的對(duì)象實(shí)現(xiàn)任何他們沒(méi)有真正實(shí)現(xiàn)的額外的接口。例如可以讓一個(gè)代理對(duì)象代理兩個(gè)目標(biāo)類。
Weaving:織入,在有了連接點(diǎn)、切點(diǎn)、通知以及切面,如何將它們應(yīng)用到程序中呢?沒(méi)錯(cuò),就是織入,在切點(diǎn)的引導(dǎo)下,將通知邏輯插入到目標(biāo)方法上,使得我們的通知邏輯在方法調(diào)用時(shí)得以執(zhí)行。
AOP proxy:AOP 代理,指在 AOP 實(shí)現(xiàn)框架中實(shí)現(xiàn)切面協(xié)議的對(duì)象。在 Spring AOP 中有兩種代理,分別是 JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理。
Target object:目標(biāo)對(duì)象,就是被代理的對(duì)象。
你知道哪幾種 AOP 框架?
主流 AOP 框架:
AspectJ:完整的 AOP 實(shí)現(xiàn)框架
Spring AOP:非完整的 AOP 實(shí)現(xiàn)框架
Spring AOP 是基于 JDK 動(dòng)態(tài)代理和 Cglib 提升實(shí)現(xiàn)的,兩種代理方式都屬于運(yùn)行時(shí)的一個(gè)方式,所以它沒(méi)有編譯時(shí)的一個(gè)處理,那么因此 Spring 是通過(guò) Java 代碼實(shí)現(xiàn)的。AspectJ 自己有一個(gè)編譯器,在編譯時(shí)期可以修改 .class 文件,在運(yùn)行時(shí)也會(huì)進(jìn)行處理。
Spring AOP 有別于其他大多數(shù) AOP 實(shí)現(xiàn)框架,目的不是提供最完整的 AOP 實(shí)現(xiàn)(盡管 Spring AOP 相當(dāng)強(qiáng)大);相反,其目的是在 AOP 實(shí)現(xiàn)和 Spring IoC 之間提供緊密的集成,以提供企業(yè)級(jí)核心特性。
Spring AOP 從未打算與 AspectJ 競(jìng)爭(zhēng)以提供全面的 AOP 解決方案,我們認(rèn)為 Spring AOP 等基于代理實(shí)現(xiàn)的框架和 AspectJ 等成熟的框架都是有價(jià)值的,并且它們是互補(bǔ)的,而不是競(jìng)爭(zhēng)關(guān)系。Spring 將 Spring AOP 和 IoC 與 AspectJ 無(wú)縫集成,以實(shí)現(xiàn) AOP 的所有功能都可以在一個(gè) Spring 應(yīng)用中。這種集成不會(huì)影響 Spring AOP API 或 AOP Alliance API,保持向后兼容。
什么是 AOP 代理?
代理模式是一種結(jié)構(gòu)性設(shè)計(jì)模式,通過(guò)代理類為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。AOP 代理是 AOP 框架中 AOP 的實(shí)現(xiàn),主要分為靜態(tài)代理和動(dòng)態(tài)代理,如下:
靜態(tài)代理:代理類需要實(shí)現(xiàn)被代理類所實(shí)現(xiàn)的接口,同時(shí)持有被代理類的引用,新增處理邏輯,進(jìn)行攔截處理,不過(guò)方法還是由被代理類的引用所執(zhí)行。靜態(tài)代理通常需要由開(kāi)發(fā)人員在編譯階段就定義好,不易于維護(hù)。
常用 OOP 繼承和組合相結(jié)合
AspectJ,在編輯階段會(huì)織入 Java 字節(jié)碼,且在運(yùn)行期間會(huì)進(jìn)行增強(qiáng)。
動(dòng)態(tài)代理:不會(huì)修改字節(jié)碼,而是在 JVM 內(nèi)存中根據(jù)目標(biāo)對(duì)象新生成一個(gè) Class 對(duì)象,這個(gè)對(duì)象包含了被代理對(duì)象的全部方法,并且在其中進(jìn)行了增強(qiáng)。
JDK 動(dòng)態(tài)代理
字節(jié)碼提升,例如 CGLIB
講講 JDK 動(dòng)態(tài)代理?
基于接口代理,通過(guò)反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的類,在調(diào)用具體方法時(shí)會(huì)調(diào)用 InvocationHandler 來(lái)處理。
需要借助 JDK 的 java.lang.reflect.Proxy 來(lái)創(chuàng)建代理對(duì)象,調(diào)用 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法創(chuàng)建一個(gè)代理對(duì)象,方法的三個(gè)入?yún)⒎謩e是:
ClassLoader loader:用于加載代理對(duì)象的 Class 類加載器Class<?>[] interfaces:代理對(duì)象需要實(shí)現(xiàn)的接口InvocationHandler h:代理對(duì)象的處理器
新生成的代理對(duì)象的 Class 對(duì)象會(huì)繼承 Proxy,且實(shí)現(xiàn)所有的入?yún)?nbsp;interfaces 中的接口,在實(shí)現(xiàn)的方法中實(shí)際是調(diào)用入?yún)?nbsp;InvocationHandler 的 invoke(..) 方法。
為什么 JDK 動(dòng)態(tài)代理只能基于接口代理,不能基于類代理?
因?yàn)?JDK 動(dòng)態(tài)代理生成的代理對(duì)象需要繼承
Proxy這個(gè)類,在 Java 中類只能是單繼承關(guān)系,無(wú)法再繼承一個(gè)代理類,所以只能基于接口代理。
為什么 InvocationHandler 不直接聲明到這個(gè)代理對(duì)象里面,而是放入繼承的
Proxy父類中?我覺(jué)得代理類既然是 JDK 動(dòng)態(tài)生成的,那么 JDK 就需要識(shí)別出哪些類是生成的代理類,哪些是非代理類,或者說(shuō) JDK 需要對(duì)代理類做統(tǒng)一的處理,這時(shí)如果沒(méi)有一個(gè)統(tǒng)一的類 Proxy 來(lái)進(jìn)行引用根本無(wú)法處理。這只是筆者的想法,具體為什么這么做不知道有小伙伴知道不 ~
講講 CGLIB 動(dòng)態(tài)代理?
JDK 動(dòng)態(tài)代理的目標(biāo)對(duì)象必須是一個(gè)接口,在我們?nèi)粘I钪校瑹o(wú)法避免開(kāi)發(fā)人員不寫(xiě)接口直接寫(xiě)類,或者根本不需要接口,直接用類進(jìn)行表達(dá)。這個(gè)時(shí)候我們就需要通過(guò)一些字節(jié)碼提升的手段,來(lái)幫助做這個(gè)事情,在運(yùn)行時(shí),非編譯時(shí),來(lái)創(chuàng)建一個(gè)新的 Class 對(duì)象,這種方式稱之為字節(jié)碼提升。在 Spring 內(nèi)部有兩個(gè)字節(jié)碼提升的框架,ASM(過(guò)于底層,直接操作字節(jié)碼)和 CGLIB(相對(duì)于前者更加簡(jiǎn)便)。
CGLIB 動(dòng)態(tài)代理則是基于類代理(字節(jié)碼提升),通過(guò) ASM(Java 字節(jié)碼的操作和分析框架)將被代理類的 class 文件加載進(jìn)來(lái),修改其字節(jié)碼生成一個(gè)子類。
需要借助于 CGLIB 的 org.springframework.cglib.proxy.Enhancer 類來(lái)創(chuàng)建代理對(duì)象,設(shè)置以下幾個(gè)屬性:
Class<?> superClass:被代理的類Callback callback:回調(diào)接口
新生成的代理對(duì)象的 Class 對(duì)象會(huì)繼承 superClass 被代理的類,在重寫(xiě)的方法中會(huì)調(diào)用 callback 回調(diào)接口(方法攔截器)進(jìn)行處理。
如果你想設(shè)置一個(gè) Callback[] 數(shù)組去處理不同的方法,那么需要設(shè)置一個(gè) CallbackFilter 篩選器,用于選擇具體的方法使用數(shù)組中的哪個(gè) Callback 去處理
JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理有什么不同?
兩者都是在 JVM 運(yùn)行時(shí)期新創(chuàng)建一個(gè) Class 對(duì)象,實(shí)例化一個(gè)代理對(duì)象,對(duì)目標(biāo)類(或接口)進(jìn)行代理。JDK 動(dòng)態(tài)代理只能基于接口進(jìn)行代理,生成的代理類實(shí)現(xiàn)了這些接口;而 CGLIB 動(dòng)態(tài)代理則是基于類進(jìn)行代理的,生成的代理類繼承目標(biāo)類,但是不能代理被 final 修飾的類,也不能重寫(xiě) final 或者 private 修飾的方法。
CGLIB 動(dòng)態(tài)代理比 JDK 動(dòng)態(tài)代理復(fù)雜許多,性能也相對(duì)比較差。
Spring AOP 和 AspectJ 有什么關(guān)聯(lián)?
Spring AOP 和 AspectJ 都是 AOP 的實(shí)現(xiàn)框架,AspectJ 是 AOP 的完整實(shí)現(xiàn),Spring AOP 則是部分實(shí)現(xiàn)。AspectJ 有一個(gè)很好的編程模型,包含了注解的方式,也包含了特殊語(yǔ)法。Spring 認(rèn)為 AspectJ 的實(shí)現(xiàn)在 AOP 體系里面是完整的,不需要在做自己的一些實(shí)現(xiàn)。
Spring AOP 整合 AspectJ 注解與 Spring IoC 容器,比 AspectJ 的使用更加簡(jiǎn)單,也支持 API 和 XML 的方式進(jìn)行使用。不過(guò) Spring AOP 僅支持方法級(jí)別的 Pointcut 攔截。
為什么 Spring AOP 底層沒(méi)有使用 AspectJ 動(dòng)態(tài)代理創(chuàng)建代理對(duì)象?
我覺(jué)得是因?yàn)?AspectJ 的特殊語(yǔ)法對(duì)于 Spring 或者 Java 開(kāi)發(fā)人員來(lái)說(shuō)不是很友好,使用起來(lái)可能有點(diǎn)困難。Spring 也選擇整合 AspectJ 的注解,使用起來(lái)非常方便。
Spring AOP 中有哪些 Advice 類型?
Around Advice,圍繞型通知器,需要主動(dòng)去觸發(fā)目標(biāo)方法的執(zhí)行,這樣可以在觸發(fā)的前后進(jìn)行相關(guān)相關(guān)邏輯處理
Before Advice,前置通知器,在目標(biāo)方法執(zhí)行前會(huì)被調(diào)用
After Advice,后置通知器
AfterReturning,在目標(biāo)方法執(zhí)行后被調(diào)用(方法執(zhí)行過(guò)程中出現(xiàn)異常不會(huì)被調(diào)用)
After,在目標(biāo)方法執(zhí)行后被調(diào)用(執(zhí)行過(guò)程出現(xiàn)異常也會(huì)被調(diào)用)
AfterThrowing,執(zhí)行過(guò)程中拋出異常后會(huì)被調(diào)用(如果異常類型匹配)
執(zhí)行順序(Spring 5.2.7 之前的版本):Around “前處理” > Before > 方法執(zhí)行 > Around “后處理” > After > AfterReturning|AfterThrowing
執(zhí)行順序(Spring 5.2.7 開(kāi)始):Around “前處理” > Before > 方法執(zhí)行 > AfterReturning|AfterThrowing > After > Around “后處理”
如下(在后續(xù)文章會(huì)進(jìn)行分析,Spring 5.1.14):

Spring AOP 中 Advisor 接口是什么?
Advisor 是 Advice 的一個(gè)容器接口,與 Advice 是一對(duì)一的關(guān)系,它的子接口 PointcutAdvisor 是 Pointcut 和 Advice 的容器接口,將 Pointcut 過(guò)濾 Joinpoint 的能力和 Advice 進(jìn)行整合,這樣一來(lái)就將兩者進(jìn)行關(guān)聯(lián)起來(lái)了。
Pointcut 提供 ClassFilter 和 MethedMatcher,分別支持篩選類和方法,通過(guò) PointcutAdvisor 和 Advice 進(jìn)行整合,可以說(shuō)是形成了一個(gè)“切面”。
簡(jiǎn)述 Spring AOP 自動(dòng)代理的實(shí)現(xiàn)
在我們有了 Join point(連接點(diǎn))、Pointcut(切點(diǎn))、Advice(通知)以及 AspectJ(切面)后,我們應(yīng)該如何將他們“織入”我們的應(yīng)用呢?在 Sping AOP 中提供了自動(dòng)代理的實(shí)現(xiàn),底層借助 JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理創(chuàng)建對(duì)象。
回顧 Spring IoC 中 Bean 的加載過(guò)程,在整個(gè)過(guò)程中,Bean 的實(shí)例化前和初始化后等生命周期階段都提供了擴(kuò)展點(diǎn),會(huì)調(diào)用相應(yīng)的 BeanPostProcessor 處理器對(duì) Bean 進(jìn)行處理。當(dāng)我們開(kāi)啟了 AspectJ 自動(dòng)代理(例如通過(guò) @EnableAspectJAutoProxy 注解),則會(huì)往 IoC 容器中注冊(cè)一個(gè) AbstractAutoProxyCreator 自動(dòng)代理對(duì)象,該對(duì)象實(shí)現(xiàn)了幾種 BeanPostProcessor,例如在每個(gè) Bean 初始化后會(huì)被調(diào)用,解析出當(dāng)前 Spring 上下文中所有的 Advisor(會(huì)緩存),如果這個(gè) Bean 需要進(jìn)行代理,則會(huì)通過(guò) JDK 動(dòng)態(tài)代理或者 CGLIB 動(dòng)態(tài)代理創(chuàng)建一個(gè)代理對(duì)象并返回,所以得到的這個(gè) Bean 實(shí)際上是一個(gè)代理對(duì)象。這樣一來(lái),開(kāi)發(fā)人員只需要配置好 AspectJ 相關(guān)信息,Spring 則會(huì)進(jìn)行自動(dòng)代理,和 Spring IoC 完美地整合在一起。
參考:《死磕Spring之IoC篇 - Bean 的創(chuàng)建過(guò)程》
請(qǐng)解釋 Spring @EnableAspectJAutoProxy 的原理?
使用了 @EnableAspectJAutoProxy 注解則會(huì)開(kāi)啟 Spring AOP 自動(dòng)代理,該注解上面有一個(gè) @Import(AspectJAutoProxyRegistrar.class) 注解,AspectJAutoProxyRegistrar 實(shí)現(xiàn)了 ImportBeanDefinitionRegistrar 這個(gè)接口,在實(shí)現(xiàn)的方法中會(huì)注冊(cè)一個(gè) AnnotationAwareAspectJAutoProxyCreator 自動(dòng)代理對(duì)象(如果沒(méi)有注冊(cè)的話),且將其優(yōu)先級(jí)設(shè)置為最高,同時(shí)解析 @EnableAspectJAutoProxy 注解的配置并進(jìn)行設(shè)置。這個(gè)自動(dòng)代理對(duì)象是一個(gè) BeanPostProcessor 處理器,在 Spring 加載一個(gè) Bean 的過(guò)程中,如果它需要被代理,那么會(huì)創(chuàng)建一個(gè)代理對(duì)象(JDK 動(dòng)態(tài)代理或者 CGLIB 動(dòng)態(tài)代理)。
除了注解的方式,也可以通過(guò) <aop:aspectj-autoproxy /> 標(biāo)簽開(kāi)啟 Spring AOP 自動(dòng)代理,原理和注解相同,同樣是注冊(cè)一個(gè)自動(dòng)代理對(duì)象。
@Import 注解的原理參考:《死磕Spring之IoC篇 - @Bean 等注解的實(shí)現(xiàn)原理》
XML 自定義標(biāo)簽的原理參考:《死磕Spring之IoC篇 - 解析自定義標(biāo)簽(XML 文件)》
Spring Configuration Class CGLIB 提升與 AOP 類代理關(guān)系?
在 Spring 底層 IoC 容器初始化后,會(huì)通過(guò) BeanDefinitionRegistryPostProcessor 對(duì)其進(jìn)行后置處理,其中會(huì)有一個(gè) ConfigurationClassPostProcessor 處理器會(huì)對(duì) @Configuration 標(biāo)注的 BeanDefinition 進(jìn)行處理,進(jìn)行 CGLIB 提升,這樣一來(lái)對(duì)于后續(xù)的 Spring AOP 工作就非常簡(jiǎn)單了,因?yàn)檫@個(gè) Bean 天然就是一個(gè) CGLIB 代理。
在 Spring 5.2 開(kāi)始 @Configuration 注解中新增了一個(gè) proxyBeanMethods 屬性(默認(rèn)為 true),支持顯示的配置是否進(jìn)行 CGLIB 提升,畢竟進(jìn)行 CGLIB 提升在啟動(dòng)過(guò)程會(huì)有一定的性能損耗,且創(chuàng)建的代理對(duì)象會(huì)占有一定的內(nèi)存,通過(guò)該配置進(jìn)行關(guān)閉,可以減少不必要的麻煩,對(duì) Java 云原生有一定的提升。
@Configuration注解的 Bean 進(jìn)行 CGLIB 提升后有什么作用呢?舉個(gè)例子,大多數(shù)情況下,
@ConfigurationClass 會(huì)通過(guò)@Bean注解為 Bean 定義,比如 @Bean User user() { return new User(); },那這樣是不是每次主動(dòng)調(diào)用這個(gè)方法都會(huì)返回一個(gè)新的 User 對(duì)象呢?不是的,
@ConfigurationClass 在得到 CGLIB 提升后,會(huì)設(shè)置一個(gè)攔截器專門(mén)對(duì)@Bean方法進(jìn)行攔截處理,通過(guò)依賴查找的方式從 IoC 容器中獲取 Bean 對(duì)象,如果是單例 Bean,那么每次都是返回同一個(gè)對(duì)象,所以當(dāng)主動(dòng)調(diào)用這個(gè)方法時(shí)獲取到的都是同一個(gè) User 對(duì)象。
參考:《死磕Spring之IoC篇 - @Bean 等注解的實(shí)現(xiàn)原理》
Sping AOP 應(yīng)用到哪些設(shè)計(jì)模式?
如下:
創(chuàng)建型模式:抽象工廠模式、工廠方法模式、構(gòu)建器模式、單例模式、原型模式
結(jié)構(gòu)型模式:適配器模式、組合模式、裝飾器模式、享元模式、代理模式
行為型模式:模板方法模式、責(zé)任鏈模式、觀察者模式、策略模式、命令模式、狀態(tài)模式
關(guān)于每種設(shè)計(jì)模式,以及在 Spring AOP 中的應(yīng)用在后續(xù)文章會(huì)進(jìn)行簡(jiǎn)單的介紹。
Spring AOP 在 Spring Framework 內(nèi)部有哪些應(yīng)用?
Spring 事件、Spring 事務(wù)、Spring 數(shù)據(jù)、Spring 緩存抽象、Spring 本地調(diào)度、Spring 整合、Spring 遠(yuǎn)程調(diào)用
粉絲福利:Java從入門(mén)到入土學(xué)習(xí)路線圖
??????

??長(zhǎng)按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
