在這裏,首先我定義了一個切面ide
該切面定義了PointCut、Advice ,以及JoinPoint,以後定義了業務類BuyService和業務類ChatService,接下來我會經過源碼跟蹤的模式講解下SpringAop作了什麼。函數
首先給出Main類源碼分析
能夠看到我這裏用的是AnnotationConfigApplicationContext,解釋下post
AnnotationConfigApplicationContext是一個用來管理註解bean的容器,因此我能夠用該容器取得我定義了@Service註解的類的實例。ui
打斷點後,啓動程序,咱們能夠看到TestDemo的實例在idea的表現是這樣的idea
而BuyService的實例卻不一樣3d
咱們能夠從看到BuyService是SpringCGLIB強化過的一個實例,那麼問題來了代理
爲何BuyService被強化過而TestDemo沒有?調試
SpringCGLIB又是什麼?cdn
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】 這個接口有什麼做用呢?具體能夠看我前陣子寫的一篇文章:
如今已經找到了掃描註解的地方,而且咱們也看到了最終是生成了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是針對類實現代理,主要是對指定的類生成一個子類,而且覆蓋其中的方法。
從上面的源碼閱讀而且分析能夠看出
強化後的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實現動態代理。