javascript
猿创征文|Spring系列框架之面向切面编程AOP
??前面的話??
本篇文章將介紹一種特別重要的思想,AOP(Aspect Oriented Programming),即面向切面編程,可以說(shuō)是OOP(Object Oriented Programming,面向?qū)ο缶幊?#xff09;的補(bǔ)充和完善。
AOP把軟件系統(tǒng)分為兩個(gè)部分:核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)。業(yè)務(wù)處理的主要流程是核心關(guān)注點(diǎn),與之關(guān)系不大的部分是橫切關(guān)注點(diǎn)。
小貼士:博主推薦->學(xué)習(xí)面試刷題必用工具
📒博客主頁(yè):未見(jiàn)花聞的博客主頁(yè)
🎉歡迎關(guān)注🔎點(diǎn)贊👍收藏??留言📝
📌本文由未見(jiàn)花聞原創(chuàng),CSDN首發(fā)!
📆首發(fā)時(shí)間:🌴2022年9月6日🌴
??堅(jiān)持和努力一定能換來(lái)詩(shī)與遠(yuǎn)方!
💭推薦書(shū)籍:📚《Spring實(shí)戰(zhàn)》,📚《SpringBoot實(shí)戰(zhàn)》
💬參考在線編程網(wǎng)站:🌐牛客網(wǎng)🌐力扣
博主的碼云gitee,平常博主寫的程序代碼都在里面。
博主的github,平常博主寫的程序代碼都在里面。
🍭作者水平很有限,如果發(fā)現(xiàn)錯(cuò)誤,一定要及時(shí)告知作者哦!感謝感謝!
📌導(dǎo)航小助手📌
- 1.面向切面編程AOP
- 1.1什么是AOP?
- 1.2AOP的作用
- 1.3AOP的核心概念
- 2.Spring AOP
- 2.1Spring AOP的使用
- 2.2AspectJ表達(dá)式基本語(yǔ)法
- 2.3拋出異常后通知與環(huán)繞通知
- 2.4Spring AOP的實(shí)現(xiàn)原理
注意事項(xiàng):博主安利一款刷題面試的神器,如果有小伙伴還沒(méi)有注冊(cè)牛客,可以點(diǎn)擊下方鏈接進(jìn)行注冊(cè),注冊(cè)完就能立即刷題了。不僅是刷題,上面還有很多有關(guān)就業(yè)的面經(jīng),面試題庫(kù),以及名企的模擬面試,我非常推薦它,博主自己用的也很多,也刷了不少題了!下圖可以作證:
注冊(cè)地址:牛客網(wǎng)
有關(guān)任何問(wèn)題都可以與博主交流,你可以在評(píng)論區(qū)留言,也可以私信我,更可以加上博主的vx與博主一對(duì)一交流(文章最下方有)。
1.面向切面編程AOP
1.1什么是AOP?
AOP(Aspect Oriented Programming),即面向切面編程,可以說(shuō)是OOP(Object Oriented Programming,面向?qū)ο缶幊?#xff09;的補(bǔ)充和完善。OOP引入封裝、繼承、多態(tài)等概念來(lái)建立一種對(duì)象層次結(jié)構(gòu),用于模擬公共行為的一個(gè)集合。不過(guò)OOP允許開(kāi)發(fā)者定義縱向的關(guān)系,但并不適合定義橫向的關(guān)系,例如日志功能。日志代碼往往橫向地散布在所有對(duì)象層次中,而與它對(duì)應(yīng)的對(duì)象的核心功能毫無(wú)關(guān)系對(duì)于其他類型的代碼,如安全性、異常處理和透明的持續(xù)性也都是如此,這種散布在各處的無(wú)關(guān)的代碼被稱為橫切(cross cutting),在OOP設(shè)計(jì)中,它導(dǎo)致了大量代碼的重復(fù),而不利于各個(gè)模塊的重用。
AOP技術(shù)恰恰相反,它利用一種稱為"橫切"的技術(shù),剖解開(kāi)封裝的對(duì)象內(nèi)部,并將那些影響了多個(gè)類的公共行為封裝到一個(gè)可重用模塊,并將其命名為"Aspect",即切面。所謂"切面",簡(jiǎn)單說(shuō)就是那些與業(yè)務(wù)無(wú)關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來(lái),便于減少系統(tǒng)的重復(fù)代碼,降低模塊之間的耦合度,并有利于未來(lái)的可操作性和可維護(hù)性。
使用"橫切"技術(shù),AOP把軟件系統(tǒng)分為兩個(gè)部分:核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)。業(yè)務(wù)處理的主要流程是核心關(guān)注點(diǎn),與之關(guān)系不大的部分是橫切關(guān)注點(diǎn)。橫切關(guān)注點(diǎn)的一個(gè)特點(diǎn)是,他們經(jīng)常發(fā)生在核心關(guān)注點(diǎn)的多處,而各處基本相似,比如權(quán)限認(rèn)證、日志、事物。AOP的作用在于分離系統(tǒng)中的各種關(guān)注點(diǎn),將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)分離開(kāi)來(lái)。
1.2AOP的作用
想象一個(gè)場(chǎng)景,我們?cè)谧龊笈_(tái)系統(tǒng)時(shí),除了登錄和注冊(cè)等幾個(gè)功能不需要做用戶登錄驗(yàn)證之外,其他幾乎所有頁(yè)面都需要先驗(yàn)證用戶登錄的狀態(tài),那這個(gè)時(shí)候我們要怎么處理呢?
如果不使用AOP,我們就需要在每一個(gè)Controller層都寫一遍驗(yàn)證用戶是否已經(jīng)登錄的程序,如果你實(shí)現(xiàn)的功能有很多,并且這些功能都需要進(jìn)行登錄驗(yàn)證,那你就需要編寫大量重復(fù)的代碼,非常的麻煩,盡管你可以將登錄驗(yàn)證實(shí)現(xiàn)的邏輯封裝在一個(gè)方法中,但是你要在很多地方調(diào)用這個(gè)方法,還是很麻煩。
如果使用AOP,在進(jìn)入核心的業(yè)務(wù)代碼之前會(huì)做統(tǒng)一的一個(gè)攔截,去驗(yàn)證用戶是否登錄,這樣就很方便,僅需做一個(gè)攔截工作,再將驗(yàn)證代碼一執(zhí)行即可。
除了登錄驗(yàn)證功能之外,還有很多功能也可以使用AOP,比如:
- 統(tǒng)一日志記錄與持久化。
- 統(tǒng)一方法執(zhí)行時(shí)間統(tǒng)計(jì)。
- 統(tǒng)一數(shù)據(jù)返回格式。
- 統(tǒng)一處理程序中的異常。
- 統(tǒng)一事務(wù)的開(kāi)啟與提交。
也就是說(shuō)使用 AOP 可以擴(kuò)充多個(gè)對(duì)象的某個(gè)能力,所以 AOP 可以說(shuō)是 OOP (Object Oriented Programming,面向?qū)ο缶幊?#xff09;的補(bǔ)充和完善。
1.3AOP的核心概念
1、橫切關(guān)注點(diǎn)
想要對(duì)哪些方法或類進(jìn)行攔截,攔截后怎么處理,這些關(guān)注點(diǎn)稱之為橫切關(guān)注點(diǎn)。
2、切面(aspect)
類是對(duì)物體特征的抽象,切面就是對(duì)橫切關(guān)注點(diǎn)的抽象,你可以認(rèn)為切面相當(dāng)于橫切關(guān)注點(diǎn)。
3、連接點(diǎn)(joinpoint)
被攔截到的點(diǎn),因?yàn)镾pring只支持方法類型的連接點(diǎn),所以在Spring中連接點(diǎn)指的就是被攔截到的方法,實(shí)際上連接點(diǎn)還可以是字段或者構(gòu)造器。
4、切入點(diǎn)(pointcut)
提供一組規(guī)則,根據(jù)規(guī)則匹配合法的連接點(diǎn),滿足規(guī)則的連接點(diǎn)可以理解為切點(diǎn),然后可以為切點(diǎn)提供具體的處理(通知)。
5、通知(advice)
所謂通知指的就是指攔截到連接點(diǎn)之后要執(zhí)行的代碼,或者說(shuō)在切點(diǎn)出所需要執(zhí)行的代碼是什么。
通知包含前置通知,后置通知,返回之后通知,拋異常后通知與環(huán)繞通知五類。
在Spring切面類中,可以在方法上使用以下注解,會(huì)設(shè)置方法為通知方法,在滿足條件后會(huì)調(diào)用對(duì)應(yīng)滿足條件的方法:
- 前置通知使用@Before∶通知方法會(huì)在目標(biāo)方法調(diào)用之前執(zhí)行。
- 后置通知使用@After∶通知方法會(huì)在目標(biāo)方法返回或者拋出異常后調(diào)用。
- 返回之后通知使用@AfterReturning∶ 通知方法會(huì)在目標(biāo)方法返回后調(diào)用。
- 拋異常后通知使用@AfterThrowing∶ 通知方法會(huì)在目標(biāo)方法拋出異常后調(diào)用。
- 環(huán)繞通知使用@Around∶通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為。
6、目標(biāo)對(duì)象
代理的目標(biāo)對(duì)象。
7、織入(weaving)
織入(weaving)即代理的生成時(shí)機(jī),
織入是把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程,切面在指定的連接點(diǎn)被織入到目標(biāo)對(duì)象中。
在目標(biāo)對(duì)象的生命周期里有多個(gè)點(diǎn)可以進(jìn)行織入∶
- 編譯期∶切面在目標(biāo)類編譯時(shí)被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
- 類加載器∶切面在目標(biāo)類加載到JVM時(shí)被織入。這種方式需要特殊的類加載器(ClassLoader),它可以在目標(biāo)類被引入應(yīng)用之前增強(qiáng)該目標(biāo)類的字節(jié)碼。AspectJ5的加載時(shí)織入(load-time weaving.LTW)就支持以這種方式織入切面。
- 運(yùn)行期∶切面在應(yīng)用運(yùn)行的某一時(shí)刻被織入。一般情況下,在織入切面時(shí),AOP容器會(huì)為目標(biāo)對(duì)象動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象。SpringAOP就是以這種方式織入切面的。
8、引入(introduction)
在不修改代碼的前提下,引入可以在運(yùn)行期為類動(dòng)態(tài)地添加一些方法或字段。
2.Spring AOP
面向切面編程是一種思想,Spring AOP是AOP的一種實(shí)現(xiàn)。
2.1Spring AOP的使用
SpringAOP使用的主要步驟為:
第一步,在SpringBoot項(xiàng)目中添加AOP相關(guān)的依賴。
第二步,定義切面。
第三步,定義切點(diǎn)。
第四步,實(shí)現(xiàn)通知。
第一步,在SpringBoot項(xiàng)目中添加AOP相關(guān)的依賴,就是在Maven的配置文件中添加aop的依賴。
由于使用Edit Starters插件訪問(wèn)官方的源是找不到有關(guān)SpringBoot的AOP依賴,這是因?yàn)樵趇dea中,上面只列舉了一些常用的依賴,不是所有依賴都在上面,如果找不到我們就去Maven中央倉(cāng)庫(kù)中去尋找。
搜索一下,找到這個(gè)依賴,然后進(jìn)去復(fù)制依賴信息拷貝到Maven的配置文件中就行。
第二步,定義切面,在spring boot項(xiàng)目中其實(shí)就是加上@Aspect和@Component注解的一個(gè)類,這個(gè)類就表示一個(gè)切面。
//設(shè)置切面,這個(gè)類就是一個(gè)切面 @Aspect @Component public class UserAspect {... }第三步,在切面里面定義切點(diǎn),在Spring中其實(shí)本質(zhì)上就是一個(gè)方法,具體說(shuō)是使用 @Pointcut注解修飾的一個(gè)方法,該方法不需要配置任何信息。
//定義切點(diǎn),設(shè)置攔截規(guī)則@Pointcut("execution(* com.example.demo.controller.UserController.* (..))")public void pointcut() {}其中@Pointcut注解中的參數(shù)是一個(gè)AspectJ表達(dá)式,它的作用就是設(shè)置哪些返回值類型哪些類的哪些方法需要攔截可以指定到參數(shù)列表。
第四步,實(shí)現(xiàn)通知,本質(zhì)上就是實(shí)現(xiàn)一個(gè)方法,只不過(guò)在方法上加上不同通知類型的注解即可,如前置通知加上@Before注解,注解的參數(shù)為切點(diǎn)方法名。
//前置通知@Before("pointcut()")public void doBefore() {System.out.println("執(zhí)行Before通知");}同理,后置通知也是如此,就是將注解改為@After:
//后置通知@After("pointcut()")public void doAfter() {System.out.println("執(zhí)行After通知");}以及目標(biāo)方法返回后通知@AfterReturning:
//返回之后通知@AfterReturning("pointcut()")public void doAfterRunning() {System.out.println("執(zhí)行AfterRunning通知");}我們來(lái)驗(yàn)證一下上述設(shè)置切面攔截代碼的正確性,我們寫一個(gè)在攔截范圍的類以及方法:
啟動(dòng)程序,我們?cè)L問(wèn)頁(yè)面http://127.0.0.1:8080/user/hello,看看控制臺(tái)的輸出:
通過(guò)運(yùn)行結(jié)果我們也能夠看出上面三種通知方式執(zhí)行的時(shí)機(jī)以及先后順序。
2.2AspectJ表達(dá)式基本語(yǔ)法
*∶匹配任意字符,只匹配一個(gè)元素(包,類,或方法,方法參數(shù))
..∶匹配任意字符,可以匹配多個(gè)元素,在表示類時(shí),必須和*聯(lián)合使用,匹配參數(shù)列表時(shí)表示匹配所有類型的參數(shù)列表。
+ ∶ 表示按照類型匹配指定類及其所有子類,必須跟在類名后面,如com.Car+,表示攔截Cat類以及繼承Cat類的所有子類。
切點(diǎn)表達(dá)式由切點(diǎn)函數(shù)組成,其中execution()是最常用的切點(diǎn)函數(shù),用來(lái)匹配方法,語(yǔ)法為∶
execution(<權(quán)限修飾符><返回類型><包.類.方法(參數(shù))><異常>)其中權(quán)限修飾符與異常項(xiàng)一般省略,返回類型方法以及參數(shù)不可省略,其他項(xiàng)可以省略。
權(quán)限修飾符:
- 填寫權(quán)限修飾符,就只會(huì)匹配相應(yīng)修飾符修飾的方法。
- 省略,權(quán)限不作為限制,所有權(quán)限修飾符的方法都會(huì)匹配。
返回類型,必須參數(shù),不可省略:
- 填寫具體返回類型,就匹配相應(yīng)返回類型的方法。
- *表示匹配所有返回值類型的方法。
包,類,一般情況下要有,但是可以省略:
- 填寫包和類,就只匹配你所規(guī)定的包或類。
- *表示匹配某目錄下所有的包或者類。
- +作用在類上,匹配該類以及繼承該類的所有子類。
方法,表示需要匹配方法的名字,參數(shù)表示需要匹配參數(shù)列表的類型,不可省略:
- 指定方法名和參數(shù)列表,就只匹配你所限定的方法。
- *可以作用在方法匹配字段上,表示匹配某類中所有的方法。
- ..可以作用在參數(shù)列表上,對(duì)參數(shù)列表類型不做限制。
異常,可以匹配拋出指定異常的方法,該參數(shù)一般省略。
下面來(lái)看幾個(gè)例子,我們來(lái)了解一下AspectJ表達(dá)式:
execution(* com.cad.demo.User.*(..))∶匹配User類里的所有方法。
execution(* com.cad.demo.User+.*(..))∶匹配User類及其子類中的所有方法。
execution(* com.cad.*.*(..))∶匹配com.cad包下的所有類的所有方法。
execution(* com.cad..*.*(..))∶匹配 com.cad 包下、子孫包下所有類的所有方法。
execution(* addUser(String,int))∶ 匹配 addUser 方法,且第一個(gè)參數(shù)類型是 String,第二個(gè)參數(shù)類型是int。
2.3拋出異常后通知與環(huán)繞通知
前面我們已經(jīng)介紹了前置通知,后者通知以及返回后通知的演示,下面我們繼續(xù)介紹剩下兩種通知,在上面已經(jīng)實(shí)現(xiàn)代碼基礎(chǔ)上,我們繼續(xù)添加通知來(lái)進(jìn)行演示。
拋出異常后通知,其實(shí)和前面三種通知的用法可以說(shuō)一模一樣,只不過(guò)只有當(dāng)程序出現(xiàn)異常的時(shí)候才會(huì)執(zhí)行該通知,寫法如下,就是在切面類中實(shí)現(xiàn)一個(gè)方法,使用@AfterThrowing注解修飾即可:
//拋異常后通知@AfterThrowing("pointcut()")public void doAfterThrowing() {System.out.println("拋出異常后,執(zhí)行AfterThrowing通知");}然后我們?cè)僭谀繕?biāo)方法中構(gòu)造一個(gè)異常,異常隨便寫一個(gè)異常就行,如算術(shù)異常:
我們?cè)L問(wèn)頁(yè)面http://127.0.0.1:8080/user/world來(lái)看一看控制臺(tái)輸出:
由于出現(xiàn)了異常,方法被強(qiáng)制終止了,沒(méi)有返回,所以沒(méi)有返回后通知。
最后還剩下一個(gè)環(huán)繞通知,環(huán)繞通知你可以理解為將前置通知和后置通知一體化了,環(huán)繞通知最常見(jiàn)的用法之一就是計(jì)算目標(biāo)方法執(zhí)行的時(shí)間是多少,使用其他通知無(wú)法做到,如果使用前置加后置通知進(jìn)行對(duì)目標(biāo)方法的計(jì)時(shí),在單線程下沒(méi)有問(wèn)題,但是在多線程下有問(wèn)題,當(dāng)一個(gè)線程正在計(jì)時(shí)時(shí),另外一個(gè)線程調(diào)用了前置通知,此時(shí)計(jì)時(shí)開(kāi)始的時(shí)間就被刷新了,那自然計(jì)算得到的目標(biāo)方法執(zhí)行時(shí)間也就不準(zhǔn)確了,而環(huán)繞通知使一體化的,不存在類似這種線程安全的問(wèn)題。
環(huán)繞通知相比于其他的三種通知的使用方法較為復(fù)雜,首先實(shí)現(xiàn)環(huán)繞通知的方法必須含有ProceedingJoinPoint類的參數(shù)和使用 @Around注解修飾,表示連接點(diǎn)的執(zhí)行進(jìn)度,方法體里面第一步是執(zhí)行環(huán)繞方法的前置通知,然后通過(guò)該類對(duì)象獲取目標(biāo)方法執(zhí)行進(jìn)度并調(diào)用,再執(zhí)行環(huán)繞通知的后置通知,最后并返回該目標(biāo)方法進(jìn)度。
@Around("pointcut()")public Object doAround(ProceedingJoinPoint joinPoint) {Object res = null;System.out.println("執(zhí)行環(huán)繞通知前置通知");try {//根據(jù)連接點(diǎn)進(jìn)度獲取目標(biāo)方法,并執(zhí)行目標(biāo)方法res = joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("環(huán)繞通知后置通知");return res;}我們?cè)L問(wèn)頁(yè)面http://127.0.0.1:8080/user/hello來(lái)看一看控制臺(tái)輸出:
我們可以基于環(huán)繞通知實(shí)現(xiàn)對(duì)目標(biāo)方法的計(jì)時(shí)功能:
實(shí)現(xiàn)思路很簡(jiǎn)單,就是在執(zhí)行目標(biāo)方法之前開(kāi)始計(jì)時(shí),執(zhí)行完目標(biāo)方法之后結(jié)束計(jì)時(shí),差值就是方法運(yùn)行的時(shí)間。
計(jì)時(shí)的方式可以使用時(shí)間戳或者spring中的StopWatch類,后者更準(zhǔn)確一點(diǎn),其實(shí)都差不多。
我們可以通過(guò)傳入的joinPoint對(duì)象獲取目標(biāo)方法的方法名以及具體所在類和包,joinPoint.getSignature().toString()就能生成目標(biāo)方法的全部有關(guān)名字的信息,我們可以加上一個(gè)方法的信息來(lái)表示哪一個(gè)方法執(zhí)行的時(shí)間。
@Around("pointcut()")public Object doTime(ProceedingJoinPoint joinPoint) {Object result = null;//System.out.println("環(huán)繞通知前置通知");String methodName = "";long start = 0;long end = 0;StopWatch stopWatch = new StopWatch();try {//執(zhí)行攔截方法start = System.currentTimeMillis();stopWatch.start();methodName = joinPoint.getSignature().toString();result = joinPoint.proceed();} catch (Throwable throwable) {throwable.printStackTrace();} finally {end = System.currentTimeMillis();stopWatch.stop();}//System.out.println("環(huán)繞通知后置通知");System.out.println(methodName + "執(zhí)行了" + (end - start) + "ms");System.out.println(methodName + "執(zhí)行了" + (stopWatch.getTotalTimeMillis()) + "ms");return result;}運(yùn)行結(jié)果:
2.4Spring AOP的實(shí)現(xiàn)原理
Spring AOP是構(gòu)建在動(dòng)態(tài)代理基礎(chǔ)上,因此 Spring對(duì)AOP的支持局限于方法級(jí)別的攔截。
Spring AOP支持JDKProxy 和CGLIBProxy方式實(shí)現(xiàn)動(dòng)態(tài)代理。默認(rèn)情況下,對(duì)于非final修飾的類,SpringAOP會(huì)基于CGLIBProxy生成代理類,CGLIBProxy生成代理類的原理就是繼承目標(biāo)類,被關(guān)鍵字final修飾的類,由于不能被繼承,所以會(huì)基于DKProxy生成代理類。
SpringAOP的本質(zhì)就是生成一個(gè)目標(biāo)對(duì)象的代理類,當(dāng)前端傳來(lái)請(qǐng)求時(shí),不會(huì)將請(qǐng)求直接交給目標(biāo)對(duì)象,而是首先代理類進(jìn)行處理,如果滿足一定的條件,才會(huì)將請(qǐng)求交給目標(biāo)對(duì)象。
如果處理請(qǐng)求前需要登錄驗(yàn)證,那么代理類會(huì)去驗(yàn)證用戶賬戶是否登錄,如果用戶登錄了才會(huì)將請(qǐng)求交給目標(biāo)對(duì)象并執(zhí)行核心業(yè)務(wù)代碼,否則代理類之間返回響應(yīng)讓用戶先登錄。
參考 & 資料
Spring3:AOP
到文章最后,再來(lái)安利一下吧,博主也是經(jīng)常使用,并且也經(jīng)常在牛客上刷題,題庫(kù)也非常豐富:學(xué)習(xí),刷題,面試,內(nèi)推都有。也歡迎與博主交流有關(guān)刷題,技術(shù)方面,以及與博主聊聊天,交個(gè)朋友也好啊,畢竟有朋自遠(yuǎn)方來(lái)!
覺(jué)得文章寫得不錯(cuò)的老鐵們,點(diǎn)贊評(píng)論關(guān)注走一波!謝謝啦!總結(jié)
以上是生活随笔為你收集整理的猿创征文|Spring系列框架之面向切面编程AOP的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 南邮ctf php decode,南邮C
- 下一篇: 基于python的人脸识别检测开题报告_