摘要:思考之所以會(huì)選擇為切入點(diǎn),是因?yàn)橥ㄟ^命名可以看出這是用來構(gòu)建代理強(qiáng)化對象的地方,并且由于是先將目標(biāo)類加載到內(nèi)存中,之后通過修改字節(jié)碼生成目標(biāo)類的子類,因此我猜測強(qiáng)化是在目標(biāo)類實(shí)例化后觸發(fā)的時(shí)候進(jìn)行的。
【干貨點(diǎn)】 此處是【好好面試】系列文的第11篇文章??赐暝撈恼拢憔涂梢粤私釹pring中Aop的相關(guān)使用和原理,并且能夠輕松解答Aop相關(guān)的面試問題。更重要的是,很多人其實(shí)一看源碼就頭大,這次專門將個(gè)人閱讀源碼的整個(gè)調(diào)試過程一步步呈現(xiàn)出來,希望對你們有一定的幫助。
上篇文章比較輕松詼諧的描述了Aop的由來和實(shí)際應(yīng)用【傳送門:https://mp.weixin.qq.com/s/tQ... 】,答應(yīng)過大家要補(bǔ)充一篇相關(guān)原理分析的文章,該篇文章會(huì)從SpringAop做了什么、相關(guān)原理一步步鋪開講。
大前提看完上篇文章都知道,我這邊定義了一個(gè)切面
該切面定義了PointCut、Advice ,以及JoinPoint,之后定義了業(yè)務(wù)類BuyService和業(yè)務(wù)類ChatService,接下來我會(huì)通過源碼跟蹤的模式講解下SpringAop做了什么。
Spring Aop做了什么【開始源碼跟蹤閱讀】首先給出Main類
可以看到我這里用的是AnnotationConfigApplicationContext,解釋下
AnnotationConfigApplicationContext是一個(gè)用來管理注解bean的容器,所以我可以用該容器取得我定義了@Service注解的類的實(shí)例。
打斷點(diǎn)后,啟動(dòng)程序,我們可以看到TestDemo的實(shí)例在idea的表現(xiàn)是這樣的
而BuyService的實(shí)例卻不同
我們可以從看到BuyService是SpringCGLIB強(qiáng)化過的一個(gè)實(shí)例,那么問題來了
為什么BuyService被強(qiáng)化過而TestDemo沒有?
SpringCGLIB又是什么?
Spring是在什么時(shí)候生成一個(gè)強(qiáng)化后的實(shí)例的?
帶著這些疑問,讓我們一步步從Spring源碼中找到答案。
為什么BuyService被強(qiáng)化過而TestDemo沒有?
這個(gè)問題比較簡單,我們可以看回上面我對切片的定義
可以從代碼中看出,我定義的切點(diǎn)是*Service命名的類,而TestDemo很明顯不符合這個(gè)設(shè)定,因此TestDemo逃過被強(qiáng)化的命運(yùn)。
SpringCGLIB又是什么?
CGLIB其實(shí)就是一種實(shí)現(xiàn)動(dòng)態(tài)代理的技術(shù),利用了ASM開源包,先將代理對象類的class文件加載進(jìn)來,之后通過修改其字節(jié)碼并且生成子類。結(jié)合demo來解讀便是SpringCGLIB會(huì)先將BuyService加載到內(nèi)存中,之后通過修改字節(jié)碼生成BuyService的子類,該子類便是強(qiáng)化后的BuyService,上文看到的強(qiáng)化后的實(shí)例便是該子類的實(shí)例。
Spring是在什么時(shí)候生成一個(gè)強(qiáng)化后的實(shí)例的?
這個(gè)便厲害了,首先,我們要先從Spring如何加載切片入手。
【思考Time】 為什么我會(huì)選擇從切片入手呢?原因很簡單,Spring就是因?yàn)榘l(fā)現(xiàn)了切片,并且對切片進(jìn)行解析后才知道了要強(qiáng)化哪些類。
切片的處理第一步便是要加上@Aspect注解,學(xué)過注解的都知道,注解的作用更多的是標(biāo)志識別,也就是告訴Spring這個(gè)類要做相關(guān)特殊處理,因此我們可以基于該認(rèn)識,反調(diào)該注解使用的地方
可以從截圖看出,我反調(diào)了@Aspect后定位到了AbstractAspectJAdvisorFactory類中的hasAspectAnnotation函數(shù),并且攜帶參數(shù)clazz,因此我猜測該接口就是用來識別clazz是否使用了注解@Aspect的地方,于是我打上了斷點(diǎn),并且加了條件 clazz == AuthAspect.class ,重新啟動(dòng)后
我們看到確實(shí)被斷點(diǎn)到了,可以得出我的猜測是對的。
我們先看下斷點(diǎn)后做了什么事情,之后再看下具體是哪里進(jìn)行了掃描。在斷點(diǎn)處按F8繼續(xù)往下走,最后發(fā)現(xiàn)
沒錯(cuò),可以看到最終是構(gòu)建成了一個(gè)Advisor對象 ,并且放入了BeanFactoryAspectJAdvisorsBuilder中的advisorsCache中,這樣意味著Spring最終會(huì)將使用了@Aspect注解的類構(gòu)建成Advisor對象后保存進(jìn)BeanFactoryAspectJAdvisorsBuilder.advisorsCache中。
接下來我們看看具體是哪里進(jìn)行了使用@Aspect注解的相關(guān)類的掃描,這次我斷點(diǎn)的地方在BeanFactoryAspectJAdvisorsBuilder中的advisorsCache調(diào)用了put的地方。
【思考Time】 為什么我會(huì)選擇在advisorsCache調(diào)用了put的地方打斷點(diǎn)呢?原因很簡單,因?yàn)槲覀兩厦嬉呀?jīng)分析出@Aspect注解的類構(gòu)建成Advisor對象后保存進(jìn)BeanFactoryAspectJAdvisorsBuilder.advisorsCache中,而我通過反調(diào)知道put的地方只有一個(gè),因此我可以斷定在此處打斷點(diǎn)可以知道到底哪里進(jìn)行了掃描的操作。
通過打斷點(diǎn)后我從idea的Frames面板中看到
沒錯(cuò),做了掃描@Aspect注解的掃描器是AbstractAutoProxyCreator類
我們可以從中看到AbstractAutoProxyCreator最終實(shí)現(xiàn)了InstantiationAwareBeanPostProcessor接口。
【思考Time】 這個(gè)接口有什么作用呢?具體可以看我前陣子寫的一篇文章:https://mp.weixin.qq.com/s/r2...
現(xiàn)在已經(jīng)找到了掃描注解的地方,并且我們也看到了最終是生成了Advisor對象 ,并且放入了BeanFactoryAspectJAdvisorsBuilder中的advisorsCache中,那么Spring是在什么時(shí)候生成強(qiáng)化后的實(shí)例的呢?
接下來我的切入點(diǎn)是AbstractAutoProxyCreator中的postProcessAfterInitialization接口。
【思考Time】 之所以會(huì)選擇AbstractAutoProxyCreator為切入點(diǎn),是因?yàn)橥ㄟ^命名可以看出這是SpringAop用來構(gòu)建代理[強(qiáng)化]對象的地方,并且由于SpringCGLIB是先將目標(biāo)類加載到內(nèi)存中,之后通過修改字節(jié)碼生成目標(biāo)類的子類,因此我猜測強(qiáng)化是在目標(biāo)類實(shí)例化后觸發(fā)postProcessAfterInitialization的時(shí)候進(jìn)行的。
因此我在postProcessAfterInitialization接口中做了斷點(diǎn),并且加了調(diào)試條件。
可以看到我這里斷點(diǎn)到了ChatService這個(gè)類。
【思考Time】 為什么專門斷點(diǎn)ChatService這個(gè)類?之所以會(huì)專門定位這個(gè)類,因?yàn)槲业那忻娴哪繕?biāo)類就包含了ChatService,通過定位到該類,我們可以一步步捕捉Spring的強(qiáng)化操作。
我們可以看到,生成強(qiáng)化后的對象就藏在wrapIfNecessary中。
【思考Time】 為什么我會(huì)知道是生成強(qiáng)化后的對象就藏在wrapIfNecessary中呢?因?yàn)槲彝ㄟ^調(diào)試發(fā)現(xiàn),在調(diào)用了wrapIfNecessary接口后,返回的對象是強(qiáng)化后的對象。
那么問題來了,為什么Spring會(huì)知道ChatService類需要進(jìn)行進(jìn)行強(qiáng)化呢?我們可以從wrapIfNecessary中走入更深一層,通過調(diào)試,可以看到
在此處會(huì)從advisorsCache中根據(jù)aspectName取出對應(yīng)的Advisor。拿到Advisor后,便是進(jìn)行過濾的地方了,通過F8往后走,可以看到過濾的地方在AopUtils.canApply接口中。
可以看到此處傳進(jìn)來的targetClass符合切面的要求,因此可以進(jìn)行構(gòu)建強(qiáng)化對象。
接下來讓我們看下真正產(chǎn)生強(qiáng)化對象的地方了
我們可以看到在AbstractAutoProxyCreator的createProxy函數(shù)中看到,最后會(huì)構(gòu)造出一個(gè)強(qiáng)化后的chatService。
那么createProxy又做了什么呢?通過斷點(diǎn)一層層深入后,發(fā)現(xiàn)最后會(huì)到達(dá)
通過源碼分析,我們發(fā)現(xiàn)在AbstractAutoProxyCreator構(gòu)建強(qiáng)化對象的時(shí)候是調(diào)用了createAopProxy函數(shù),重點(diǎn)來了,我們可以看到針對targetClass,也就是ChatService做了判斷,如果targetClass有實(shí)現(xiàn)接口或者targetClass是Proxy的子類,那么使用的是JDK的動(dòng)態(tài)代理實(shí)現(xiàn)AOP,如果不是才會(huì)使用CGLIB實(shí)現(xiàn)動(dòng)態(tài)代理。
那么JDK實(shí)現(xiàn)的動(dòng)態(tài)代理和CGLIB實(shí)現(xiàn)的動(dòng)態(tài)代理有什么區(qū)別嗎?
首先動(dòng)態(tài)代理可以分為兩種:JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。從文中我們也可以看出,當(dāng)目標(biāo)類有接口的時(shí)候才會(huì)使用JDK動(dòng)態(tài)代理,其實(shí)是因?yàn)镴DK動(dòng)態(tài)代理無法代理一個(gè)沒有接口的類。JDK動(dòng)態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,而CGLIB是針對類實(shí)現(xiàn)代理,主要是對指定的類生成一個(gè)子類,并且覆蓋其中的方法。
本來想一篇文章說完源碼跟蹤分析Aop和Aop的實(shí)現(xiàn)機(jī)制代理模式,發(fā)現(xiàn)源碼跟蹤分析已經(jīng)很占篇幅了,因此沒辦法只能再開一篇文章專門闡述Aop的實(shí)現(xiàn)機(jī)制代理模式,期待下篇文章。
文章總結(jié)從上面的源碼閱讀并且分析可以看出
強(qiáng)化后的ChatService實(shí)例是在ChatService實(shí)例化后產(chǎn)生的,也就是AbstractAutoProxyCreator.postProcessAfterInitialization后。
之所以Spring能夠識別的出來為什么ChatService實(shí)例需要進(jìn)行強(qiáng)化,是因?yàn)樵谶@一步之前Spring先使用AbstractAutoProxyCreator掃描了使用注解@Aspect的類,并且構(gòu)造成了Advisor對象后放入了advisorsCache中。
從advisorsCache取出來后對ChatService類進(jìn)行識別,使用的是AopUtils.canApply。識別通過后,便會(huì)走入AbstractAutoProxyCreator.createProxy函數(shù)中,從中構(gòu)建真正的強(qiáng)化對象。
在構(gòu)建強(qiáng)化對象的時(shí)候,走的是DefaultAopProxyFactory.createAopProxy,并且會(huì)對目標(biāo)類進(jìn)行判斷,如果targetClass有實(shí)現(xiàn)接口或者targetClass是Proxy的子類,那么使用的是JDK的動(dòng)態(tài)代理實(shí)現(xiàn)AOP,如果不是才會(huì)使用CGLIB實(shí)現(xiàn)動(dòng)態(tài)代理。
公眾號主營:服務(wù)端編程相關(guān)技術(shù)解說,具體可以看歷史文章。
公眾號副業(yè):各種陪聊吹水,包括技術(shù)、就業(yè)、人生經(jīng)歷、大學(xué)生活、內(nèi)推等等。
歡迎關(guān)注,一起侃大山
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/75458.html
摘要:總結(jié)動(dòng)態(tài)代理的相關(guān)原理已經(jīng)講解完畢,接下來讓我們回答以下幾個(gè)思考題。 【干貨點(diǎn)】 此處是【好好面試】系列文的第12篇文章。文章目標(biāo)主要是通過原理剖析的方式解答Aop動(dòng)態(tài)代理的面試熱點(diǎn)問題,通過一步步提出問題和了解原理的方式,我們可以記得更深更牢,進(jìn)而解決被面試官卡住喉嚨的情況。問題如下 SpringBoot默認(rèn)代理類型是什么 為什么不用靜態(tài)代理 JDK動(dòng)態(tài)代理原理 CGLIB動(dòng)態(tài)代理...
摘要:干貨點(diǎn)此處是好好面試系列文的第篇文章。而這也是出現(xiàn)的原因,沒錯(cuò),就是被設(shè)計(jì)出來彌補(bǔ)短板的。運(yùn)行結(jié)果如下運(yùn)行結(jié)果可想而知,的通過驗(yàn)證,的失敗。 【干貨點(diǎn)】此處是【好好面試】系列文的第10篇文章??赐暝撈恼拢憔涂梢粤私釹pring中Aop的相關(guān)使用和原理,并且能夠輕松解答Aop相關(guān)的面試問題。 在實(shí)際研發(fā)中,Spring是我們經(jīng)常會(huì)使用的框架,畢竟它們太火了,也因此Spring相關(guān)的知...
摘要:目錄如何用提高效率后端掘金經(jīng)常有人說我應(yīng)該學(xué)一門語言,比如之類,但是卻不知道如何入門。本文將通過我是如何開發(fā)公司年會(huì)抽獎(jiǎng)系統(tǒng)的后端掘金需求出現(xiàn)年會(huì)將近,而年會(huì)抽獎(jiǎng)環(huán)節(jié)必不可少,但是抽獎(jiǎng)系統(tǒng)卻還沒有。 云盤一個(gè)個(gè)倒下怎么辦?無需編碼,手把手教你搭建至尊私享云盤 - 工具資源 - 掘金微盤掛了,360倒了,百度云盤也立了Flag。能讓我們在云端儲(chǔ)存分享文件的服務(wù)越來越少了。 買一堆移動(dòng)硬盤...
摘要:插件開發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實(shí)現(xiàn)文件分片斷點(diǎn)續(xù)傳。 Vue.js 插件開發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡單的方式。插....
閱讀 3937·2021-11-22 13:54
閱讀 2708·2021-09-30 09:48
閱讀 2386·2021-09-28 09:36
閱讀 3139·2021-09-22 15:26
閱讀 1370·2019-08-30 15:55
閱讀 2535·2019-08-30 15:54
閱讀 1452·2019-08-30 14:17
閱讀 2364·2019-08-28 18:25