Spring AOP介紹及源碼分析

軟件開發經歷了從彙編語言到高級語言和從過程化編程到面向對象編程;前者是爲了提高開發效率,而後者則使用了歸納法,把具有共性的東西進行歸類並使之模塊化,達到便於維護和擴展的目的;如果說面向對象編程可以對業務需求進行很好的分解使之模塊化;那麼面向切面編程AOP(Aspect-Oriented Programming)則可以對系統需求進行很好的模軟件開發經歷了從彙編語言到高級語言和從過程化編程到面向對象編程;前者是爲了提高開發效率,而後者則使用了歸納法,把具有共性的東西進行歸類並使之模塊化,達到便於維護和擴展的目的;如果說面向對象編程可以對業務需求進行很好的分解使之模塊化;那麼面向切面編程AOP(Aspect-Oriented Programming)則可以對系統需求進行很好的模塊組織,簡化系統需求和實現之間的對比關係,是對OOP思想的一種補充;塊組織,簡化系統需求和實現之間的對比關係,是對OOP思想的一種補充;

一、AOP介紹

舉個例子來說明一下吧!現在系統中有很多的業務方法,如上傳產品信息、修改產品信息、發佈公司庫等;現在需要對這些方法的執行做性能監控,看每個業務方法的執行時間;在不改變原業務代碼的基礎上,也許我們會這麼做:

Offer接口:
這裏寫圖片描述

Offer實現:
這裏寫圖片描述

Offer代理:
這裏寫圖片描述

我們要通過下面的方式來使用:
這裏寫圖片描述

上面的例子的輸出爲:
這裏寫圖片描述

上面的例子中,OfferProxy實現了IOffer,而所有的業務實現均委託給其成員offer;可以想像,這應該就是最簡單的AOP的實現了;但這種方式會存在一個問題:如果有非常多的這種業務對象需要性能監控,我們就需要寫同樣多的XyzProxy來滿足需求,這也是非常巨大的工作量。

上面說到了代理,我們先看看代理模式吧!

二、代理模式及實現

下面是代理模式的類圖:

這裏寫圖片描述

代理模式類圖

代理模式中,存在一個稱爲ProxyObject的代理對象和RealObject的真實對象,它們都實現了相同的接口;在調用的地方持有ProxyObject的實例,當調用request()方法時,ProxyObject可以在執行RealObject.request()前後做一些特定的業務,甚至不調用RealObject.request()方法。

目前實現代理模式的方式有兩種:基於JDK的動態代理和基於CGLIB字節碼的代理。

2.1 JDK動態代理

JDK動態代理,顧名思義,是基於JDK的反射(reflect)機制;在JDK中,提供了InvocationHandler這個接口,下面是JDK裏面的註釋:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

簡單翻譯,意思是說:該接口由被代理對象的handler所實現;當調用代理對象的方法時,該方法調用將被編碼,然後交給代理對象的invoke方法去執行;

是不是有一種豁然開朗的感覺呢?沒錯,答案就在你心中。

這樣,上面的代碼就可以改成下面的實現方式:

這裏寫圖片描述

調用端:
這裏寫圖片描述

通過這種方式,你不需要爲針對每一個業務寫一個代理對象,就可以很輕鬆地完成你的需求;但也許你已經注意到了,JDK的動態代理,在創建代理對象(上面紅色代碼部分)時,被代理的對象需要實現接口(即面向接口編程);

這就是JDK的動態代理,簡單吧!下面看看CGLIB代理方式。

2.2 CGLIB代理

如果目標對象沒有實現任何接口,那怎麼辦呢?不用擔心,你可以用CGLIB來實現代理:
這裏寫圖片描述

調用端:
這裏寫圖片描述

使用CGLIB創建的代理對象,其實就是繼承了要代理的目標類,然後對目標類中所有非final方法進行覆蓋,但在覆蓋方法時會添加一些攔截代碼(上面CglibProxyFactory類中的intercept方法)。

下面看看Spring中是如何實現AOP的。

三、Spring AOP的實現

3.1 Spring AOP的幾個概念

Spring AOP中的幾個基本概念,每次學習AOP都被這幾個概念折騰的很不爽,我們在這裏再把這幾個概念描述一遍,力爭把這幾個概念搞清,在每次review這塊內容的時候可以很快上手。

1.切面(Aspect):切面就是一個關注點的模塊化,如事務管理、日誌管理、權限管理等;

2.連接點(Joinpoint):程序執行時的某個特定的點,在Spring中就是一個方法的執行;

3.通知(Advice):通知就是在切面的某個連接點上執行的操作,也就是事務管理、日誌管理等;

4.切入點(Pointcut):切入點就是描述某一類選定的連接點,也就是指定某一類要織入通知的方法;

5.目標對象(Target):就是被AOP動態代理的目標對象;

用一張圖來形象地表達AOP的概念及其關係如下:

這裏寫圖片描述

3.2 Spring AOP中切入點、通知、切面的實現

理解了上面的幾個概念後,我們分別來看看Spring AOP是如何實現這些概念的;

1.切入點(Pointcut):它定義了哪些連接點需要被織入橫切邏輯;在Java中,連接點對應哪些類(接口)的方法。因此,我們都能猜到,所謂的切入點,就是定義了匹配哪些婁的哪些方法的一些規則,可以是靜態的基於類(方法)名的值匹配,也可以是基於正則表達式的模式匹配。來看看Spring AOP Pointcut相關的類圖:
這裏寫圖片描述

在Pointcut接口的定義中,也許你已經想到了,ClassFilter是類過濾器,它定義了哪些類名需要攔截;典型的兩個實現類爲TypePatternClassFilter和TrueClassFilter(所有類均匹配);而MethodMatcher爲方法匹配器,定義哪些方法需要攔截。

在上面的類圖中:

StaticMethodMatch與DynamicMethodMatch的區別是後者在運行時會依據方法的參數值進行匹配。
NameMatchMethodPointCut根據指定的mappedNames來匹配方法。
AbstractRegexpMethodPointCut根據正則表達式來匹配方法。
1.通知(Advice):通知定義了具體的橫切邏輯。在Spring中,存在兩種類型的Advice,即per-class和per-instance的Advice。

所謂per-class,即該類型的Advice只提供方法攔截,不會爲目標對象保存任何狀態或者添加新的特性,它也是我們最常見的Advice。下面是per-class的類圖:

這裏寫圖片描述

BeforeAdvice:在連接點前執行的橫切邏輯。
AfterReturningAdvice:在連接點執行後,再執行橫切邏輯。
AfterAdvice:一般由程序自己實現,當拋出異常後,執行橫切邏輯。
AroundAdvice:Spring AOP中並沒有提供這個接口,而是採用了AOP Alliance的MethodInteceptor接口;通過看AfterReturningAdvice的源碼我們知道,它是不能更改連接點所在方法的返回值的(更改引用);但使用的MethodInteceptor,所有的事情,都不在話下。
在上面的類圖中,還有兩種類沒有介紹,那就是***AdviceAdapter和***AdviceInteceptor,我們以AfterReturningAdviceInterceptor爲例來說明:

這裏寫圖片描述

該類實現了MethodInterceptor和AfterAdvice接口,同時構造函數中還有一個AfterReturningAdvice實例的參數;這個類存在的作用是爲了什麼呢?對,沒錯,Spring AOP把所有的Advice都適配成了MethodInterceptor,統一的好處是方便後面橫切邏輯的執行(參看下一節),適配的工作即由***AdviceAdapter完成;

哈哈,Spring AOP的代碼也不過如此嘛:所謂的AfterReturningAdvice,通過適配成MethodInterceptor後,其實就是在invoke方法中,先執行目標對象的方法,再執行的AfterReturningAdvice所定義的橫切邏輯。你現在明白它爲什麼不能修改返回值的引用了吧?

對於per-instance的Advice,目前只有一種實現,就是Introduction,使用的場景比較少,有興趣的同學可以自己研究一下,呵呵!

1.切面(Aspect):在Spring中,Advisor就是切面;但與通常的Aspect不同的是,Advisor通常只有一個Pointcut和一個Advice,而Aspect則可以包含多個Pointcut和多個Advice,因此Advisor是一種特殊的Aspect。但,這已經夠用了!

接下來看下per-class Advisor的類圖:

這裏寫圖片描述

其實沒有什麼好看的,前面已經說過,Advisor包含一個Pointcut和一個Advisor;在AbstractGenericPointcutAdvisor中,持有一個Advice的引用;下面的幾個實現,均是針對前面提到的幾種不同的Pointcut的實現。

3.3 Spring AOP實現的基本線索

我們選擇ProxyFactoryBean作爲入口點和分析的開始。ProxyFactoryBean是在Spring IoC環境中,創建AOP應用的最底層方法,從中,可以看到一條實現AOP的基本線索。

所有的邏輯從以下的方法開始,我們主要針對單例的代理對象的生成:

這裏寫圖片描述

下面我們深入到SpringAOP核心代碼的內部,看看代理對象的生成機制,攔截器橫切邏輯以及織入的實現。

3.4 代理對象的生成

對於getSingletonInstance()方法返回了什麼,這就是代理對象如何產生的邏輯了,然我們鬚根溯源,看看傳說中的proxy到底是如何一步一步的產生的。

這裏寫圖片描述

ProxyFactoryBean是AdvisedSupport的子類,Spring使用AopProxy接口把AOP代理的實現與框架的其他部分分離開來。在AdvisedSupport中通過這樣的方式來得到AopProxy,當然這裏需要得到AopProxyFactory的幫助 ,從JDK或者cglib中得到想要的代理對象:

這裏寫圖片描述

這個DefaultAopProxyFactory是Spring用來生成AopProxy的地方,它包含JDK和Cglib兩種實現方式。讓我接着往裏面看:

這裏寫圖片描述

可以看到其中的代理對象可以由JDK或者Cglib來生成,JdkDynamicAopProxy類和Cglib2AopProxy都實現的是AopProxy的接口,我們進入JdkDynamicAopProxy實現中看看Proxy是怎樣生成的:

這裏寫圖片描述

用Proxy包裝target之後,通過ProxyFactoryBean得到對其方法的調用就被Proxy攔截了, ProxyFactoryBean的getObject()方法得到的實際上是一個Proxy了,target對象已經被封裝了。對 ProxyFactoryBean這個工廠bean而言,其生產出來的對象是封裝了目標對象的代理對象。

3.5 攔截器的作用

前面分析了SpringAOP實現中得到Proxy對象的過程,接下來我們去探尋Spring AOP中攔截器鏈是怎樣被調用的,也就是Proxy模式是怎樣起作用的。

還記得在JdkDynamicAopProxy中生成Proxy對象的時候,有一句這樣的代碼嗎?

return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);

這裏我們的JdkDynamicAopProxy實現了InvocationHandler這個接口,this參數對應的是InvocationHandler對象,也就是說當 Proxy對象的函數被調用的時候,InvocationHandler的invoke方法會被作爲回調函數調用:

這裏寫圖片描述

上面所說的目標對象方法的調用,是通過AopUtils的方法調用,使用反射機制來對目標對象的方法進行的:

這裏寫圖片描述

接下來,我們來看具體的ReflectiveMethodInvocation中proceed()方法的實現,也就是攔截器鏈的實現機制:
這裏寫圖片描述

從上面的分析我們看到了Spring AOP攔截機制的基本實現,比如Spring怎樣得到Proxy,怎樣利用JAVA Proxy以及反射機制對用戶定義的攔截器鏈進行處理。

3.6 織入的實現

在上面調用攔截器的時候,經過一系列的註冊,適配的過程以後,攔截器在攔截的時候,會調用到預置好的一個通知適配器,設置通知攔截器,這是一系列Spring設計好爲通知服務的類的一個,是最終完成通知攔截和實現的地方,例如對 MethodBeforeAdviceInterceptor的實現是這樣的:

這裏寫圖片描述

可以看到通知適配器將advice適配成Interceptor以後,會調用advice的before方法去執行橫切邏輯。這樣就成功的完成了before通知的織入。

原文地址:Spring AOP介紹及源碼分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章