從源碼入手,一文帶你讀懂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的,可以說是一款面向未來的工具,後續也值得我們深入學習。)

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

創建一個Bean,TestBean。

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

創建AOP的Aspect。

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

然後寫一個啟動類,測試以上配置

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

運行結果:

com.zack.demo.TestBean.getStr()開始執行... getStr():Testing! com.zack.demo.TestBean.getStr()方法結束...

Demo: github.com/Zack-Ku/spr…

示例解析與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的過程。

以上概念看起來可以還比較難懂,可以通過以下一圖(來源於網絡)來理解

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

請各位讀者和各位程序員,在閱讀源碼的時候,一定要先搞清楚基本概念,和一定一定要知道對應概念的英文,否則在看源碼的時候,根本對不上號,使理解難度大大提高。因為源碼都是英文寫的。

至此AOP的基本使用和概念相信大家都有一定的瞭解,下面開始從源碼入手,去探索整個Spring AOP的實現。

源碼分析

上面的例子之所以能完成AOP的代理,只因為Spring的xml配置裡面加了這一句

< aop : aspectj-autoproxy / >

加上了這一個配置,使得整個Spring項目擁有了AOP的功能。全局搜索下aspectj-autoproxy這個字段,可以發現,是這個類AspectJAutoProxyBeanDefinitionParser解析了這個元素。

其中的parse方法調用的是AopNamespaceUtils類中的registerAspectJAnnotationAutoProxyCreatorIfNecessary。這個方法作用是初始化一個AOP專用的Bean,並且註冊到Spring容器中。

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

解析這三個操作,

  1. 第一句,註冊一個AnnotationAwareAspectJAutoProxyCreator(稱它為自動代理器),這個Creator是AOP的操作核心,也是掃描Bean,代理Bean的操作所在。
  2. 第二句,解析配置元素,決定代理的模式。其中有JDK動態代理,還有CGLIB代理,這部分後續會再細講。
  3. 第三句,作為系統組件,把Creator這個Bean,放到Spring容器中。讓Spring實例化,啟動這個Creator。

自動代理器

下面我們來細看AnnotationAwareAspectJAutoProxyCreator是怎麼對Bean做AOP的。

AnnotationAwareAspectJAutoProxyCreator的父類AbstractAutoProxyCreator,裡面實現了BeanPostProceesor接口的postProcessAfterInitialization方法(該方法在一個Bean加載到Spring後會執行)。

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

關聯註釋描述可知,當一個bean加載完後,執行了該方法,會生成一個新的代理對象,返回context中加載。

下面重點看其中的wrapIfNecessary方法。講述了整個AOP的核心流程,是Spring AOP最最最核心的代碼所在。

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

看到紅框的兩個核心方法,可以知道,先從剛加載的Bean中掃描出所有的advice和advisor,然後用它來創建一個代理對象。

獲取Advisor

先看如何掃描出advice和advisor。 一步步Debug getAdvicesAndAdvisorsForBean(),找到BeanFactoryAspectJAdvisorsBuilder中的buildAspectJAdvisors方法。

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

該方法就是找出Spring容器中存在的AspectBean,然後返回所有AspectBean中的Advisor。

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

示例中,LogAspect就是AspectBean,然後LogAspect中的before和after方法就是Advisor。 所以最終返回了LogAspect中的Advisor(before和after)。

創建代理

拿到了所有的Advisor後,就進入了創建代理的流程了createProxy()。

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

這些入參,對比上一篇講過的動態代理,其實非常相似。

  • beanClass,加載到Spring,觸發AOP的bean類
  • targetSource,目標對象,示例中則是從ctx中取出的testBean
  • specificInterceptors,指定Advisor,示例中則是before和after的方法。

下面來具體看下代理的過程

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

代碼可以概括為,創建一個proxyFactory對象,然後把上面的參數都丟到這個這個工廠裡,最後從proxyFactory獲取一個代理對象。

來看看ProxyFactory的getProxy方法是怎麼生成代理對象的。

Debug該方法,可以在DefaultAopProxyFactory中createAopProxy看到

從源碼入手,一文帶你讀懂Spring AOP面向切面編程

工廠會根據配置與目標對象的類型,選擇用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源碼,一步步地調試。

關注我:私信回覆“架構資料”獲取往期Java高級架構資料、源碼、筆記、視頻、BATJ面試視頻資料


分享到:


相關文章: