? ? Spring之所以成為java工程師界的“南天一柱谤碳,萬世楷牟男梗”悠鞍,除了有幫我們解決對象管理的IOC(可見我上一篇文章)染簇,還有對面向?qū)ο缶幊痰挠辛ρa充的AOP(Aspect Oriented Programming)面向切面編程参滴。還是那個古老的話題,專業(yè)術(shù)語可以幫我簡化交流復(fù)雜度锻弓,卻對概念進行了封裝砾赔,讓其變得晦澀難懂。所以我們需要搞清楚的第一點就是何為“面向切面”弥咪?為什么一定需要“面向切面”过蹂?不需要行不行?
? ? 這里補充一句聚至,昨天有朋友跟我反饋了下酷勺,希望我可以多聊些Spring里的設(shè)計模式。首先我非常感謝有這些能夠認真看完我這個菜雞博客并且經(jīng)過思考還提出寶貴意見的朋友扳躬,也正是你們我才能樂此不疲的繼續(xù)寫下去^_^脆诉!但我個人的觀點是我們拿到一個問題,正常邏輯是先去分析問題贷币,然后解決問題击胜,至于解決方案,那是仁者見仁智者見智的事兒役纹。就像面向切面也不是非要動態(tài)代理不可偶摔,用內(nèi)部類一樣可以做到。當(dāng)然了促脉,Spring AOP中確實相比于IOC要復(fù)雜的多辰斋,所以自然有相當(dāng)?shù)牟诲e的設(shè)計模式模式,后面咱們再細聊這個問題瘸味。首先宫仗,我們還是先把目光放到我們最開始的問題上,不忘初心嘛~~
? ? 軟件工程旁仿,不僅僅是實現(xiàn)一個功能就完事了藕夫,隨著時間的推移,版本的迭代枯冈,會產(chǎn)生不計其數(shù)的需求變更毅贮。有一句笑話叫作“寫時一時爽,重構(gòu)火葬場”講的就是寫代碼的人沒有考慮過未來的變更場景尘奏,導(dǎo)致修改時候的工作量如同再造滩褥,那你加班加到猝死不能也怪資本主義丑陋的面目吧,哈哈哈罪既。所以我們希望的就是能否有這樣的一個功能铸题,當(dāng)我們要往1000個類上面加一個功能時候,不需要修改這1000個類琢感,而是有一個名為切面的類丢间,里面寫上我們需要添加的功能,然后分別切開那1000個類驹针,悄悄的幫我們把功能填進去烘挫。也正是這么個奇思妙想,造就了現(xiàn)如今的偉大的AOP模塊柬甥!
? ? 在我剛開始學(xué)習(xí)Spring AOP的時候也去網(wǎng)上找了很多博客饮六,其實他們大致都是千篇一律的講JDK動態(tài)代理和Cglib,然后大刀闊斧的在講代理模式苛蒲,適配器模式這類設(shè)計模式卤橄。我不是覺得他們講的不好講的不對。只是他們都在很細化的講“廬山”的某一花某一草某一木臂外,我看完之后還是不是很清楚這個“廬山”真面目窟扑。我不知道你們有沒想過這個問題。那些博客說了那么多代理對象原理漏健,請問這個代理對象什么時候?qū)⑽疫@個正常的對象給貍貓換太子成代理對象嚎货?所以,為了給我的好奇心買單蔫浆,我開始著手于AOP的源碼殖属。
? ? Spring的IOC是基石,所以即便是AOP也會將對象托管給IOC容器瓦盛∠聪裕回到最開始的需求,我既然把對象給你Spring管理谭溉,自然存在向你索取的需求墙懂,所以我還是以getBean為線索展開。開始之前扮念,我先解釋一下幾個概念损搬,因為Spring的類的含義往往從它的名字中暴露出來,可以減少閱讀源碼負擔(dān)柜与。
1.? Advice:通知巧勤。雖然我至今還不清楚老外為什么取名字,所以不管它語言含義弄匕。Advice是AOP聯(lián)盟定義的接口颅悉,代表切面行為。Spring自身提供了三種切面切入的方式迁匠,落實到Spring源碼中的類是MethodBeforeAdviceInterceptor剩瓶,AfterReturningAdviceInterceptor驹溃,ThrowsAdviceInterceptor。也就是我們熟悉在目標方法之前之前執(zhí)行切面邏輯延曙,目標方法返回之前執(zhí)行切面邏輯豌鹤,拋出異常的時候執(zhí)行切面邏輯
2.? PointCut:切點。這個比較容易理解枝缔,就是要被代理的方法布疙,也就是我們所說的目標方法
3. Advisor:通知器。封裝Advice愿卸,PointCut灵临。因為我們通常在配置文件中聲明一個切面配置的時候,Advice和Pointcut都是成對出現(xiàn)的趴荸,不是嗎儒溉?
????上述的匹配方法是采用的AspectJ的庫函數(shù),具體做法目前不是我們關(guān)心的重點发钝,其實也就是玩轉(zhuǎn)字符串睁搭,匹配 <property name ="expression" value ="execution(* main.Knight.say(..))">?中的Knight是不是和我這個bean(類型為main.Knight)一致,就像JSON工具包笼平,雖然我也不知道他的具體做法园骆,但我知道底層大致的做法足矣。
? ? 如此一來寓调,我們就清楚了AOP是如何做到“貍貓換太子”了锌唾。接下來就是眾多博客都有講解的JDK Proxy 和Cglib做法了。Spring源碼在決定生成代理對象的時候會去判斷下目標類有沒有實現(xiàn)接口夺英,如果實現(xiàn)了接口就生成Proxy代理對象晌涕,如果沒有實現(xiàn)接口就通過Cglib生成一個類去繼承目標類,復(fù)寫目標類中需要加入切面邏輯的方法痛悯,所以目標類方法不能聲明稱final類型的余黎。這就是二者的區(qū)別!這不是我最關(guān)心的地方载萌,其實若想知道他們二者的本質(zhì)區(qū)別惧财,只要寫一個小demo,然后用反編譯的工具就可以一探二者究竟扭仁,這里就不贅述了垮衷,下面我也只介紹Proxy。
? ? Spring中JDK 的Proxy類名曰JdkDynamicAopProxy乖坠,也就是我們耳熟能詳?shù)膭討B(tài)代理搀突。這個類主要實現(xiàn)了AopProxy,InvocationHandler接口
? ? 所謂的代理模式就是熊泵,當(dāng)你對代理對象調(diào)用目標類的方法時候仰迁,JVM都會觸發(fā)上圖這個invoke方法甸昏,以改變方法調(diào)用的軌跡,實現(xiàn)切面邏輯的悄悄織入徐许,這是被API封裝掉的筒扒,如果想落實真相,可以去反編譯绊寻。我們先看看invoke里面都做了什么?
? ? 沒錯,methodInvocation.procceed()就是一個回調(diào)方法。
? ? 看到?jīng)]有爽锥,這個procceed就是通過反射的機制調(diào)用目標對象的方法空执。只不過他的調(diào)用需要交給攔截器來調(diào)用,也就是所謂的回調(diào)供汛。
? ? 如此一來Spring實現(xiàn)AOP的脈絡(luò)就很清楚了,真實的Spring AOP是非常非常復(fù)雜的。除非親自去看梯皿,不然很難在有限的篇幅講清楚,而且本人還是那句話县恕,解決方案是應(yīng)運著需求的东羹,技術(shù)總是后知后覺得,有印象的朋友可能會記得我在前面提到過Spring AOP有用到適配器這種設(shè)計模式忠烛,為什么我到現(xiàn)在都沒提及過属提,那是因為我給出都是Spring AOP代理過程中最理想最簡單的情況,自然不需要想盡辦法利用多態(tài)的性質(zhì)去簡化代碼美尸。
? ? 還記得我前面留下的問題冤议,為什么methodInterceptor就當(dāng)它是Advice了。因為我給出的切面邏輯是單一的师坎,真實的使用切面邏輯是三個恕酸,before,afterReturning胯陋,throws蕊温。所以Spring源碼在執(zhí)行目標方法之前是有一個攔截鏈的,里面有多個攔截器遏乔,這多個攔截器執(zhí)行完成之后才會執(zhí)行目標方法寿弱。
? ? 適配器是吧,直接上圖按灶!
????Spring源碼是想干啥呢症革?我解釋一下,我現(xiàn)在手上有一堆從配置文件獲得的Advice鸯旁,但我想獲得我想要的攔截器噪矛,因為攔截器才是真正改變目標方法的地方量蕊。但是有由于advice這個對象究竟是before,afterReturning還是throws不得而知艇挨,所以我們用適配器去適配它残炮,目的是為了生成對應(yīng)的不同的攔截器。adapters是一個List缩滨,里面分別裝有MethodBeforeAdviceAdapter势就,AfterReturningAdviceAdapter,ThrowsAdviceAdapter脉漏。我們的做法就是遍歷它們苞冯,然后去supportsAdvice()。下面我舉一個AfterReturningAdvice例子侧巨,其他兩個可類推舅锄。
? ? 這就是所謂的適配器設(shè)計模式。本質(zhì)還是面向?qū)ο蟮亩鄳B(tài)司忱,不同的玩法而已皇忿,而且instanceof不到萬不得以的地步不要使用,因為這樣會是代碼的擴展性變得不好坦仍。至于簡單工廠鳍烁,單例模式這種老掉牙的設(shè)計模式就不必也來Spring中湊數(shù)了
? ? 好了,Spring AOP也落下帷幕了繁扎。我雖然還沒工作老翘,但是實習(xí)也大半年了,我自己的感覺真實生產(chǎn)中Spring IOC比AOP使用的要多一些锻离,或者說AOP是衣架子铺峭,先人已經(jīng)做的挺好的,我們只需要改一改換一換衣架上的衣服罷了汽纠。而且Spring AOP的使用意識其實比Spring AOP的原理在生產(chǎn)中來的更實際一些卫键。
? ? 最后,分享一波阿里云的面試心得:我今年2月份的時候面試了下阿里云的實習(xí)生虱朵。其中有一個面試題就是針對我在上一家公司的業(yè)務(wù)莉炉,業(yè)務(wù)是這樣的:鏡像刪除,既要將數(shù)據(jù)庫中的鏡像信息刪除碴犬,還要將調(diào)用鏡像倉庫服務(wù)進程的API絮宁,實現(xiàn)鏡像描述信息和實際的鏡像文件都刪除。我的回答也比較耿直服协,因為確實是這么做的绍昂,我說我先將數(shù)據(jù)庫中的鏡像信息刪除,然后刪除鏡像文件,并且為了提升前端的響應(yīng)速度窘游,我采用異步的方式唠椭,創(chuàng)建子線程的方式去調(diào)鏡像倉庫的API。面試官問:那如果數(shù)據(jù)庫出現(xiàn)異常會怎么樣忍饰?我:數(shù)據(jù)會回滾贪嫂,這是Spring的事務(wù)管理機制。面試官:好艾蓝,那數(shù)據(jù)回滾了力崇,鏡像倉庫的鏡像沒了,你怎么辦赢织?我:亮靴。。敌厘。
? ? 結(jié)果可想而知,我掛了朽合。其實這是一個非常簡單的問題俱两,只怪當(dāng)初我對AOP的理解和數(shù)據(jù)庫事務(wù)的理解太過僵硬。首先我沒有抓住事務(wù)的本質(zhì)曹步,為什么只有操作數(shù)據(jù)庫才算是一個事務(wù)宪彩?事務(wù)的本質(zhì)就是將一堆行為作為一個整體來看,要么一起成功要么一起失敗讲婚。就像數(shù)據(jù)庫刪除鏡像信息和刪除鏡像文件尿孔,這兩樣事情就是要么一起成功要么一起失敗,所以應(yīng)該將二者視為一個事務(wù)筹麸。
? ? 因此解決方案很簡單活合,只要在這個業(yè)務(wù)函數(shù)的整體上加一個@Transaction即可,@Transaction就是一個AOP物赶,在業(yè)務(wù)函數(shù)開始之前transaction.start()白指。然后try catch住整個方法,無論數(shù)據(jù)庫還是調(diào)用鏡像倉庫API只要拋出異常執(zhí)行transaction,rollback()酵紫。方法返回之前調(diào)用transaction.commit()告嘲。是不是就對應(yīng)了before,afterReturning奖地,throws啊橄唬。
? ? Spring的實現(xiàn)是復(fù)雜的,如何講清楚他参歹,從構(gòu)思到攥寫都是相當(dāng)耗時耗力的仰楚。我也總算告一段落了,希望這兩篇博客可以給予JAVA開發(fā)的同學(xué)一點幫助。
????后續(xù)的話我還會寫一些Mysql缸血,Hadoop蜜氨,Kubernetes博客,希望技術(shù)不再是逼格的代表捎泻,而是藝術(shù)的化身飒炎!