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