1. 程式人生 > >Spring AOP介紹及原始碼分析

Spring AOP介紹及原始碼分析

一、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通知的織入。

本文轉自:http://www.uml.org.cn/j2ee/201301102.asp