之前《零基礎帶你看Spring原始碼——IOC控制反轉》詳細講了Spring容器的初始化和載入的原理,後面《你真的完全瞭解Java動態代理嗎?看這篇就夠了》介紹了下JDK的動態代理。
基於這兩者的實現上,這次來探索下Spring的AOP原理。雖然AOP是基於Spring容器和動態代理,但不瞭解這兩者原理也絲毫不影響理解AOP的原理實現,因為大家起碼都會用。
AOP,Aspect Oriented Programming,面向切面程式設計。在很多時候我們寫一些功能的時候,不需要用到繼承這麼重的方法,例如對每個方法在執行前打log,在沒有AOP的情況下,我們只能對每個方法都寫一句打log的語句。如果是一個複雜點的功能,那麼將會產生許多重複的程式碼,而且會對模組之間有更多的耦合。 然而,在AOP下,我們只需要通過特定的方法,就能直接切入程式碼,新增自定義的功能(後續再講AOP裡面的概念點)。
下面將從一個簡單的示例入手,拆解示例的內容,通過原始碼分析,一步步帶大家讀懂AOP的原理實現。
使用示例
以下程式碼不以文字形式展示,若需要程式碼,可以到github檢視完整Demo。
Demo:github.com/Zack-Ku/spr…
Spring專案依然是用xml最原始的配置方式,為了只是能簡單地閱讀原理,否則會多很多自動配置的內容在裡面。而AOP的配置用的是註解形式,因為畢竟看起來畢竟清晰,容易理解邏輯。
建立一個Gradle專案,新增對應的Spring與AOP的依賴。 (Gradle和Maven類似,都是自動化構建的工具。但與Maven相比,Gradle是基於groovy,採用DSL格式,具有更強的靈活性、簡潔性、擴充性。現在連Spring的官方原始碼都是用Gradle的,可以說是一款面向未來的工具,後續也值得我們深入學習。)
建立一個Bean,TestBean。 建立AOP的Aspect。 然後寫一個啟動類,測試以上配置 執行結果:com.zack.demo.TestBean.getStr()開始執行... getStr():Testing! com.zack.demo.TestBean.getStr()方法結束...
示例解析與AOP術語概念
看到上面的結果,很容易猜想到,LogAspect作用了在TestBean上,使得每次執行TestBean上的方法時,都會執行對應的方法(before/after)。
LogAspect中帶註解@Pointcut的allMethod(),是用來掃描程式中的連線點。當執行一個方法時,命中了連線點,則會根據不同的通知,執行對應的織入程式碼。在上面例子中,執行getStr()前會執行LogAspect中的before(),執行getStr()後會執行LogAspect中的after()。
具體的通知包含
- @Before,前置通知,執行方法前執行
- @AfterReturn,返回通知,正常返回方法後執行
- @After,後置通知,方法最終結束後執行,相當於finaly
- @Around,環繞通知,圍繞整個方法
- @AfterThrowing,異常通知,丟擲異常後執行
開發者在命中連線點時,可以通過以上不同的通知,執行對應方法。這就是AOP中的Advisor。
以上的內容其實已經把AOP核心的概念都已經點出來了,我們再深入具體的認識下其中的術語,
- Aspect,切面,一個關注點的模組。 例子中,LogAspect就是切面。
- JoinPoint, 連線點,程式執行中的某個點,某個位置。 例子中,testBean.getStr()是連線點。
- PointCut,切點,切面匹配連線點的點,一般與切點表示式相關,就是切面如何切點。 例子中,@PointCut註解就是切點表示式,匹配對應的連線點
- Advice,通知,指在切面的某個特定的連線點上執行的動作。 例子中,before()與after()方法中的程式碼。
- TargetObject,目標物件,指被切入的物件。 例子中,從ctx中取出的testBean則是目標物件。
- Weave,織入,將Advice作用在JoinPoint的過程。
以上概念看起來可以還比較難懂,可以通過以下一圖(來源於網路)來理解
請各位讀者和各位程式設計師,在閱讀原始碼的時候,一定要先搞清楚基本概念,和一定一定要知道對應概念的英文,否則在看原始碼的時候,根本對不上號,使理解難度大大提高。因為原始碼都是英文寫的。
至此AOP的基本使用和概念相信大家都有一定的瞭解,下面開始從原始碼入手,去探索整個Spring AOP的實現。
原始碼分析
上面的例子之所以能完成AOP的代理,只因為Spring的xml配置裡面加了這一句
< aop : aspectj-autoproxy / >
加上了這一個配置,使得整個Spring專案擁有了AOP的功能。全域性搜尋下aspectj-autoproxy這個欄位,可以發現,是這個類AspectJAutoProxyBeanDefinitionParser解析了這個元素。
其中的parse方法呼叫的是AopNamespaceUtils類中的registerAspectJAnnotationAutoProxyCreatorIfNecessary。這個方法作用是初始化一個AOP專用的Bean,並且註冊到Spring容器中。
解析這三個操作,- 第一句,註冊一個AnnotationAwareAspectJAutoProxyCreator(稱它為自動代理器),這個Creator是AOP的操作核心,也是掃描Bean,代理Bean的操作所在。
- 第二句,解析配置元素,決定代理的模式。其中有JDK動態代理,還有CGLIB代理,這部分後續會再細講。
- 第三句,作為系統元件,把Creator這個Bean,放到Spring容器中。讓Spring例項化,啟動這個Creator。
自動代理器
下面我們來細看AnnotationAwareAspectJAutoProxyCreator是怎麼對Bean做AOP的。
AnnotationAwareAspectJAutoProxyCreator的父類AbstractAutoProxyCreator,裡面實現了BeanPostProceesor介面的postProcessAfterInitialization方法(該方法在一個Bean載入到Spring後會執行)。
關聯註釋描述可知,當一個bean載入完後,執行了該方法,會生成一個新的代理物件,返回context中載入。下面重點看其中的wrapIfNecessary方法。講述了整個AOP的核心流程,是Spring AOP最最最核心的程式碼所在。
看到紅框的兩個核心方法,可以知道,先從剛載入的Bean中掃描出所有的advice和advisor,然後用它來建立一個代理物件。獲取Advisor
先看如何掃描出advice和advisor。 一步步Debug getAdvicesAndAdvisorsForBean(),找到BeanFactoryAspectJAdvisorsBuilder中的buildAspectJAdvisors方法。
該方法就是找出Spring容器中存在的AspectBean,然後返回所有AspectBean中的Advisor。 示例中,LogAspect就是AspectBean,然後LogAspect中的before和after方法就是Advisor。 所以最終返回了LogAspect中的Advisor(before和after)。建立代理
拿到了所有的Advisor後,就進入了建立代理的流程了createProxy()。
這些入參,對比上一篇講過的動態代理,其實非常相似。- beanClass,載入到Spring,觸發AOP的bean類
- targetSource,目標物件,示例中則是從ctx中取出的testBean
- specificInterceptors,指定Advisor,示例中則是before和after的方法。
下面來具體看下代理的過程
程式碼可以概括為,建立一個proxyFactory物件,然後把上面的引數都丟到這個這個工廠裡,最後從proxyFactory獲取一個代理物件。來看看ProxyFactory的getProxy方法是怎麼生成代理物件的。
Debug該方法,可以在DefaultAopProxyFactory中createAopProxy看到
工廠會根據配置與目標物件的型別,選擇用JDK動態代理(參考《你真的完全瞭解Java動態代理嗎?看這篇就夠了》)還是CGLIB的代理(CGLIB具體在後續講)。代理後的物件放回ctx中,然後當程式執行的時候,會直接呼叫這個代理類。
至此整個AOP的代理流程就結束了。下面來了解下CGLIG代理與JDK代理的不同
CGLIB與JDK代理區別
CGLIB(Code Generation Library)是一個強大的,高效能,高質量的Code生成類庫。它可以在執行期擴充套件Java類與實現Java介面。Hibernate支援它來實現PO(Persistent Object 持久化物件)位元組碼的動態生成。
回顧下JDK代理,JDK代理需要一組需要實現的介面,然後通過這些介面獲取構造方法,用這個構造方法和InvocationHandler,例項化一個物件出來。所以JDK的方式是基於介面的。
而CGLIB的代理是基於類的,用目標類生成一個子類,子類重寫父類的方法,從而達到動態代理的效果。CGLIB的使用和實現等後面有機會再詳細介紹。目前暫時只要理解兩者不同的使用場景就足夠了。
總結
回顧下Spring AOP的流程
- Spring載入自動代理器AnnotationAwareAspectJAutoProxyCreator,當作一個系統元件。
- 當一個bean載入到Spring中時,會觸發自動代理器中的bean後置處理
- bean後置處理,會先掃描bean中所有的Advisor
- 然後用這些Adviosr和其他引數構建ProxyFactory
- ProxyFactory會根據配置和目標物件的型別尋找代理的方式(JDK動態代理或CGLIG代理)
- 然後代理出來的物件放回context中,完成Spring AOP代理
相信大家通過閱讀本文,對Spring的AOP處理有一定的認識。想更深入地瞭解,探索每一步,每一行程式碼的實現,可以下載Demo原始碼,一步步地除錯。
Demo:github.com/Zack-Ku/spr…
更多技術文章、精彩乾貨,請關注
部落格:zackku.com
微信公眾號:Zack說碼