1. 程式人生 > >從原始碼入手,一文帶你讀懂Spring AOP面向切面程式設計

從原始碼入手,一文帶你讀懂Spring AOP面向切面程式設計

基於這兩者的實現上,這次來探索下Spring的AOP原理。雖然AOP是基於Spring容器和動態代理,但不瞭解這兩者原理也絲毫不影響理解AOP的原理實現,因為大家起碼都會用。

AOP,Aspect Oriented Programming,面向切面程式設計。在很多時候我們寫一些功能的時候,不需要用到繼承這麼重的方法,例如對每個方法在執行前打log,在沒有AOP的情況下,我們只能對每個方法都寫一句打log的語句。如果是一個複雜點的功能,那麼將會產生許多重複的程式碼,而且會對模組之間有更多的耦合。然而,在AOP下,我們只需要通過特定的方法,就能直接切入程式碼,新增自定義的功能(後續再講AOP裡面的概念點)。

下面將從一個簡單的示例入手,拆解示例的內容,通過原始碼分析,一步步帶大家讀懂AOP的原理實現。

使用示例

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容器中。解析這三個操作,

  1. 第一句,註冊一個AnnotationAwareAspectJAutoProxyCreator(稱它為自動代理器),這個Creator是AOP的操作核心,也是掃描Bean,代理Bean的操作所在。
  2. 第二句,解析配置元素,決定代理的模式。其中有JDK動態代理,還有CGLIB代理,這部分後續會再細講。
  3. 第三句,作為系統元件,把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的流程

  1. Spring載入自動代理器AnnotationAwareAspectJAutoProxyCreator,當作一個系統元件。
  2. 當一個bean載入到Spring中時,會觸發自動代理器中的bean後置處理
  3. bean後置處理,會先掃描bean中所有的Advisor
  4. 然後用這些Adviosr和其他引數構建ProxyFactory
  5. ProxyFactory會根據配置和目標物件的型別尋找代理的方式(JDK動態代理或CGLIG代理)
  6. 然後代理出來的物件放回context中,完成Spring AOP代理

相信大家通過閱讀本文,對Spring的AOP處理有一定的認識。想更深入地瞭解,探索每一步,每一行程式碼的實現,可以下載Demo原始碼,一步步地除錯Demo:https://github.com/Zack-Ku/sp...

更多技術文章、精彩乾貨,請關注部落格:zackku.com微信公眾號:Zack說碼