圖文詳解 Spring AOP,看完必懂!
往期熱門文章:
1、面試官:內(nèi)存耗盡后Redis會發(fā)生什么 ?
2、監(jiān)控告警滿飛天,運(yùn)維在家睡到自然醒...
3、@Bean與@Component 同時(shí)作用同一個(gè)類,會怎么樣?
4、閑魚面試:Thread.sleep(0) 到底有什么用?
來源:blog.csdn.net/duxd185120/article/details/109210224
學(xué)習(xí)一個(gè)模塊的設(shè)計(jì)主要是看接口設(shè)計(jì),通過接口設(shè)計(jì)我們就能夠從整體知道模塊怎么實(shí)現(xiàn)的,具體實(shí)現(xiàn)就是組裝這些接口來進(jìn)行實(shí)現(xiàn)的,知道了模塊接口設(shè)計(jì),實(shí)現(xiàn)也就變得很簡單了。
本文主要從aop背景出發(fā)點(diǎn),來自己去想需要哪些接口,就能夠描述一個(gè)模塊的功能設(shè)計(jì)規(guī)則。
AOP產(chǎn)生背景
使用面向?qū)ο缶幊?( OOP )有一些弊端,當(dāng)需要為多個(gè)不具有繼承關(guān)系的對象引人同一個(gè)公共行為時(shí),例如日志、安全檢測等,我們只有在每個(gè)對象里引用公共行為,這樣程序中就產(chǎn)生了大量的重復(fù)代碼,程序就不便于維護(hù)了。
所以就有了一個(gè)對面向?qū)ο缶幊痰难a(bǔ)充,即面向方面編程 ( AOP ), AOP 所關(guān)注的方向是橫向的,區(qū)別于 OOP 的縱向。
什么是AOP
什么是面向方面編程,3個(gè)過程:
找到橫切點(diǎn):首要目標(biāo)確定在程序的哪個(gè)位置進(jìn)行橫切邏輯 橫切邏輯(業(yè)務(wù)代碼):橫切邏輯代碼,這個(gè)就是橫切業(yè)務(wù)代碼,與aop無關(guān) 織入:將橫切邏輯織入到橫切點(diǎn)
開發(fā)者主要關(guān)心的是橫切邏輯的編寫,只需要很少的代碼編寫確定橫切點(diǎn)有哪些,而不需要去為每個(gè)橫切點(diǎn)添加橫切邏輯,不然就是面向?qū)ο缶幊塘恕?/p>
既然是橫向的編程,那么在我們的程序中,哪些可以作為橫線切入點(diǎn)呢?
看下示例代碼:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????//@1
???????B?b?=?new?B();
???????//@2
???????b.method();
???????//@3
????????B.say();
????}
????static?class?B?{
????????//字段
????????//@4
????????private?String?name;
????????//構(gòu)造方法
????????public?B()?{
????????????//@1.1
????????????}
????????//對象方法
????????public?void?method(){
????????????//@2.2
????????}
????????//靜態(tài)方法
????????static?void?say(){
????????????//@3.3
????????}
????}
}
所以我們可以將橫切點(diǎn)主要分為兩大類:字段、方法。方法又分為很多種,
橫切點(diǎn)有很多地方,從代碼上看得見的,有如下幾個(gè)地方:
使用構(gòu)造函數(shù)創(chuàng)建對象 構(gòu)造函數(shù)執(zhí)行 對象方法調(diào)用 對象方法執(zhí)行 靜態(tài)方法調(diào)用 靜態(tài)方法執(zhí)行 反射讀寫對象字段
目標(biāo)1:找到橫切點(diǎn)
那么怎么去定義一個(gè)橫切點(diǎn)呢?怎么用一個(gè)接口來描述一個(gè)橫切點(diǎn)呢?
在Java中,一切皆對象,在Java中一個(gè)類有2方面內(nèi)容:字段、方法(構(gòu)造函數(shù)、對象方法、靜態(tài)方法),java中使用AccessibleObject來抽象公共行為。方法:就是一段可以執(zhí)行的程序,一段代碼。
所以在橫切點(diǎn)接口中,首先一個(gè)功能就是返回給用戶當(dāng)前橫切點(diǎn),有兩種情況:
如果橫切點(diǎn)作用于對象(對象字段、對象方法、構(gòu)造函數(shù)),則不僅需要返回AccessibleObject,還需要返回當(dāng)前對象,因?yàn)檎{(diào)用通過反射調(diào)用對象方法需要傳入當(dāng)前對象。 如果橫切點(diǎn)作用于類,則僅返回AccessibleObject即可。
另一個(gè)接口功能就是要不要考慮在橫切點(diǎn)來控制多個(gè)橫切邏輯的調(diào)用。這個(gè)可以有框架支持,也可以由橫切點(diǎn)控制。這對應(yīng)的就是責(zé)任鏈模式的API設(shè)計(jì)。比如tomcat中的Filter鏈?zhǔn)秸{(diào)用就是以集合形式調(diào)用;netty中的Handler組織就是以鏈表形式。如果是以集合形式調(diào)用,則在橫切點(diǎn)接口需要定義一個(gè)方法來鏈?zhǔn)秸{(diào)用。(aop聯(lián)盟的JoinPoint采用是集合形式調(diào)用)
那么AOP聯(lián)盟使用JointPoint接口來定義橫切點(diǎn)。
public?interface?Joinpoint?{
?Object?proceed()?throws?Throwable;
?Object?getThis();
?AccessibleObject?getStaticPart();
}
Object proceed() throws Throwable: 鏈?zhǔn)秸{(diào)用橫切點(diǎn)
Object getThis(); 返回連接點(diǎn)當(dāng)前對象。如果當(dāng)前連接點(diǎn)是靜態(tài),比如靜態(tài)方法,則該方法返回null,因?yàn)榉瓷洳恍枰獙ο螅异o態(tài)方法是通過類調(diào)用的,壓根就沒有對象,所以返回null。spring aop不支持靜態(tài)方法的攔截,所以在spring中這里返回的就是目標(biāo)對象(被代理對象)
AccessibleObject getStaticPart(); 返回連接點(diǎn)靜態(tài)部分,對于連接點(diǎn)是方法,返回的就是Method對象。
現(xiàn)在對連接點(diǎn)的設(shè)計(jì)比較清晰了,然后就是對連接點(diǎn)的擴(kuò)展了,比如可執(zhí)行程序(構(gòu)造方法、Method)的子接口,字段的子接口(aop聯(lián)盟沒有定義,只有方法級別的)。
AOP聯(lián)盟對連接點(diǎn)接口的設(shè)計(jì):

比如在MethodInvocation,就是返回Method。
目標(biāo)2:橫切邏輯(增強(qiáng))抽象定義
增強(qiáng)的抽象,其實(shí)就需要連接點(diǎn)信息,畢竟增強(qiáng)是要投入到一個(gè)地方的,所以需要連接點(diǎn)信息。
在aop聯(lián)盟的接口定義:
Advice作為一個(gè)tag標(biāo)識,在aop聯(lián)盟中使用攔截器來作為增強(qiáng)的命名,這里完全可以去掉Interceptor,而直接定義一個(gè)MethodAdvice。之所以定義為Interceptor,是因?yàn)閿r截器命名更符合編程命名規(guī)范,讓人從命名就知道接口功能。
在MethodInterceptor,傳入連接點(diǎn)信息(因?yàn)槭欠椒〝r截,所以這里是方法級別的連接點(diǎn)接口定義)
Object?invoke(MethodInvocation?invocation)?throws?Throwable;
目標(biāo)3:織入
首先就是怎么織入。織入由兩種方案。
靜態(tài)織入:采用自定義類加載器機(jī)制。自定義類加載器根據(jù)織入規(guī)則在加載class文件期間對class文件動手織入橫切邏輯,然后將改動后的class文件交給JVM運(yùn)行。 動態(tài)織入:由多種選擇,動態(tài)代理(JDK Proxy)、動態(tài)字節(jié)碼生成技術(shù)(cglib)
spring采用動態(tài)織入。動態(tài)織入就是生成代理對象,代理對象中維護(hù)了當(dāng)前連接點(diǎn)所有攔截器,然后調(diào)用目標(biāo)方法時(shí)被代理類攔截,在代理類中作aop功能。
來一個(gè)完整的流程圖:
Spring AOP的實(shí)現(xiàn)基于AOP聯(lián)盟接口標(biāo)準(zhǔn)設(shè)計(jì)實(shí)現(xiàn)的,全局看下aopalliance有哪些接口以及接口的API設(shè)計(jì),我們上面已經(jīng)分析完了。
AOP聯(lián)盟的接口很少:
最近熱文閱讀:
1、面試官:內(nèi)存耗盡后Redis會發(fā)生什么 ? 2、監(jiān)控告警滿飛天,運(yùn)維在家睡到自然醒... 3、@Bean與@Component 同時(shí)作用同一個(gè)類,會怎么樣? 4、閑魚面試:Thread.sleep(0) 到底有什么用? 5、身為程序員碰到最奇葩的需求是怎樣的? 6、因?yàn)锽itMap,白白搭進(jìn)去8臺服務(wù)器... 7、面試官:private修飾的方法可以通過反射訪問,那么private的意義是什么? 8、如何選擇合適的分布式ID生成方案 9、面試題:為什么數(shù)據(jù)庫連接池不采用 IO 多路復(fù)用? 10、SpringBoot 配置文件敏感信息如何加密? 關(guān)注公眾號,你想要的Java都在這里
