【乾貨點】 此處是【好好面試】系列文的第11篇文章。看完該篇文章,你就能夠了解Spring中Aop的相關使用和原理,而且可以輕鬆解答Aop相關的面試問題。更重要的是,不少人其實一看源碼就頭大,此次專門將我的閱讀源碼的整個調試過程一步步呈現出來,但願對大家有必定的幫助。面試
上篇文章比較輕鬆詼諧的描述了Aop的由來和實際應用【傳送門:mp.weixin.qq.com/s/tQLO-lF_H… 】,答應過你們要補充一篇相關原理分析的文章,該篇文章會從SpringAop作了什麼、相關原理一步步鋪開講。ide
看完上篇文章都知道,我這邊定義了一個切面函數
該切面定義了PointCut、Advice ,以及JoinPoint,以後定義了業務類BuyService和業務類ChatService,接下來我會經過源碼跟蹤的模式講解下SpringAop作了什麼。源碼分析
首先給出Main類post
能夠看到我這裏用的是AnnotationConfigApplicationContext,解釋下學習
AnnotationConfigApplicationContext是一個用來管理註解bean的容器,因此我能夠用該容器取得我定義了@Service註解的類的實例。ui
打斷點後,啓動程序,咱們能夠看到TestDemo的實例在idea的表現是這樣的idea
而BuyService的實例卻不一樣3d
咱們能夠從看到BuyService是SpringCGLIB強化過的一個實例,那麼問題來了代理
帶着這些疑問,讓咱們一步步從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】 這個接口有什麼做用呢?具體能夠看我前陣子寫的一篇文章:mp.weixin.qq.com/s/r2OEqsap6…
如今已經找到了掃描註解的地方,而且咱們也看到了最終是生成了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的實現機制代理模式,期待下篇文章。
從上面的源碼閱讀而且分析能夠看出
認識個人都知道,我寫的文章都會花費大量時間整理相關資料,本身瞭解透徹後纔敢寫相關文章,目的很簡單,我想經過這種方式學習到更多東西,而後用本身的話和理解進行輸出。以前有朋友說我發文太慢,勸我能夠發發熱點文章,這樣漲粉也快點,我拒絕了,我想保持本身的初衷,經過學習後才作產出,不想浪費時間在對本身沒有成長的文章上。
感謝這麼久了一直關注個人朋友,感謝沒有由於個人產出慢而取關,但願能夠一塊兒成長!!!