《Spring技術內幕》---------SpringAop的實現
1.Spring AOP的概述
Aspect是一種新的模組化機制,用來描述分散在物件、類或函式中的橫切關注點
使用AOP後,不僅可以將這些重複的程式碼抽取出來單獨維護,在需要使用時,統一呼叫,還可以為如何使用這些公共程式碼提供豐富靈活的手段。
1.2Advice通知
定義連線點在做什麼,為切面增強提供織入介面,例如BeforeAdvice、AfterAdvice、ThrowsAdvice等;
BeforeAdvice繼承體系中,定義了為待增強的目標方法設定的前置增強介面MethodBeforeAdvice,使用這個前置介面需要實現一個回撥函式
Before方法的實現在advice中被配置到目標方法後,會在呼叫目標方法時被回撥,具體呼叫引數:method物件,這個引數是目標方法的反射物件;Object[]物件陣列,包含目標方法的形參;實現一個功能統計被呼叫的方法次數,根據呼叫方法的方法名進行統計,把統計結果根據方法名,呼叫次數放入map中;CountingBeforeAdvice是介面MethodBeforeAdvice的實現類;
Count是MethodCounter類中的方法;首先通過目標方法的反射物件,得到方法名,然後進行累加,把統計結果放到維護的map中
AfterAdvice通知型別,AfterReturningAdvice是比較常用的一個,介面中定義了一個方法
在這個Advice通知被正確配置以後,在目標方法呼叫結束併成功返回的時候,介面會被AOP回撥。回撥引數,目標方法的返回結果,方法的反射物件,方法引數,呼叫target裡面的該方法。依然用上面的例子
方法呼叫和前面一樣,只是發生的時間不一樣。如果實現不同的aop通知介面,就會被aop編織到不同的呼叫場合中。一個是在方法呼叫前實現切面增強,一個是在方法呼叫返回結果後實現增強,相同程式碼可以靈活出現在不同的應用場合。
ThrowsAdvice,沒有指定需要實現的介面方法,他在丟擲異常時被回撥,這個回撥是aop使用反射機制來完成的。
1.3 Pointcut切點
決定Advice通知應該作用於哪個連線點,就是說通過Pointcut來定義需要增強的方法的集合,這些集合的選取可以按照一定的規則來完成。Pointcut通常意味著標識的方法。
Pointcut介面定義中,需要返回一個MethodMatcher,對於Point的方法匹配判斷功能,就是用MethodMatcher實現的、以正則表示式切點JdkRegexMethodPointcut為例;JdkRegexMethodPointcut是StaticMatcherPointcut的子類,同時也是MethodMatcher的子類。
在Pointcut中,MethodMatcher物件實際上可以被配置成JdkRegexMethodPointcut來完成方法的匹配判斷的。在JdkRegexMethodPointcut中,可以看到一個matches方法,這個matches方法是MethodMatcher定義的介面方法。JdkRegexMethodPointcut中,這個matches方法就是使用正則表示式來對方法名進行匹配的地方。
ClassFilter
當織入的目標物件和Point指定的型別相同時,返回true,否則返回false,即意味著不會對這個型別的目標物件進行織入操作
如果型別對所捕捉的 Joinpoint 無所謂,那麼 Pointcut 中使用的 ClassFilter 可以直接使用ClassFilter TRUE = TrueClassFilter.INSTANCE
MethodMatcher
MethodMatcher 通過過載,定義了兩個 matches 方法,而這兩個方法的分界線就是 isRuntime 方法,這裡要特別注意!
注意到三引數的matches方法中,最後一個引數是args,因此也就可以想到:兩個 mathcers 方法的區別在於,在進行方法攔截的時候,是否匹配方法的引數
根據是否對方法的引數進行匹配,Pointcut可以分為StaticMethodMatcher和DynamicMethodMatcher,當isRuntime()返回false,表明不對引數進行匹配,為StaticMethodMatcher,返回true時,表示要對引數進行匹配,為DynamicMethodMatcher。
一般情況下,DynamicMethodMatcher會影響效能,所以我們一般使用StaticMethodMatcher就行了
1.4 Advisor通知器
Advice 通知和Pointcut切點設計完成後,需要Advisor將他們連線起來。通過Advisor,可以定義應該使用哪個Advice並在哪個Pointcut使用它。以DefaultPointcutAdvisor為例,有兩個屬性,advice和pointcut,分別配置Advice和Pointcut。
Pointcut.True的值為 TruePointcut.INSTANCE;單例模式
1.5. JoinPoint連線點
被攔截到的點,因為Spring只支援方法型別的連線點,所以在Spring中連線點指的就是被攔截到的方法。AOP中僅支援方法級別的JoinPoint,更確切的說,只支援方法執行 (Method Execution )型別的 Joinpoint,雖然 Spring AOP 僅提供方法攔截,但是實際的開發過程中,這已經可以滿足 80% 的開發需求了。
Spring AOP 之所以如此,主要有以下幾個原因。
1. Spring AOP 要提供一個簡單而強大的 AOP 框架,並不想因大而全使得框架本身過於臃腫。能夠僅付出 20% 的努力,就能夠得到 80% 的回報。否則,事倍功半,並不是想看到的結果。
2. 對於類中屬性 (Field )級別的 Joinpoint ,如果提供這個級別的攔截,那麼就破壞了面向物件的封裝,而且,完全可以通過 setter 和 getter 方法的攔截達到同樣的目的。
3. 如果應用需求非常特殊,完全超出了 Spring AOP 提供的那 80% 的需求支援,可以求助於現有其他 AOP 實現產品,如 AspectJ。 目前看來, AspectJ 是 Java 平臺對 AOP 支援最完善的產品,同時,Spring AOP 也提供了對 Aspect的支援。
2.Spring AOP的設計與實現
2.1 JVM動態代理特性
Reflection包中看到的Proxy物件,這個物件生成後,所起的作用就類似於Proxy模式中的Proxy物件,在使用時,還需要為代理物件設計一個回撥方法,在其中加入作為代理需要額外處理的操作。Jdk中,需要實現下面所示的InvocationHandler介面
這個invoke方法第一個引數就是代理物件的例項,第二個是Method方法物件,代表的是當前代理物件中被呼叫的方法對應的介面方法物件;第三個為被呼叫的方法引數。
3.建立AopProxy代理物件
Spring的AOP模組中,一個主要的部分就是代理物件的生成,而對於Spring應用,是通過配置和呼叫Spring的ProxyFactoryBean來完成這個任務的。
3.1 配置ProxyFactoryBean
基於XML配置Spring的Bean時,往往需要一系列的配置步驟來使用ProxyFactoryBean和AOP、
①定義使用的通知器Advisor,這個通知器應該作為一個bean來定義,通知器內部,定義了advice通知
②定義ProxyFactoryBean,把他作為另一個Bean,封裝AOP功能的主要類,需要設定相關的屬性,proxyInterface、interceptorNames和target,interceptorNames屬性的值往往設定為需要定義的通知器或者通知,因為這些通知器在ProxyFactoryBean的aop配置下,通過使用代理物件的攔截器起作用。
③定義target屬性,作為target屬性注入的bean,是需要用aop通知器中的切面應用來增強的物件,介面的實現類。
3.2ProxyFactoryBean生成AopProxy代理物件
從FactoryBean中獲取物件,是以getObject方法作為入口完成的,ProxyFactoryBean實現中的getObject方法,是FactoryBean需要實現的介面方法。對ProxyFactoryBean來說,把需要對target目標物件增加的通知處理,都通過getObject方法進行封裝。
對通知器鏈進行初始化,通知器鏈封裝了一系列的攔截器,這些攔截器都要從配置中讀取,然後為代理物件的生成做好準備。Spring中有singleton和prototype型別的兩種bean,需要區分。
為Proxy代理物件配置Advisor鏈,是在initializeAdvisorChain方法中完成的,有一個標誌位AdvisorChainInitialized,用來表示通知器鏈是否已經初始化,如果已經初始化,就直接返回。即初始化只發生在應用第一次通過ProxyFactoryBean去獲取代理物件的時候。初始化之後,讀取配置中出現的所有通知器,使用getBean就行,引數為通知器的名字,然後把從容器中取得通知器加入攔截器中,由addAdvisorOnChainCreation實現。
生成singleton的代理物件在getSingletonInstance程式碼中完成,這個方法是ProxyFactoryBean生成AopProxy代理物件的呼叫入口;代理物件會封裝對target目標物件的方法呼叫,target物件的方法會被代理物件攔截。首先讀取ProxyFactoryBean中的配置,為生成代理物件做好準備,設定方法呼叫介面等,
獲取目標物件的介面,然後設定介面和classLoader,呼叫createAopProxy方法,內部使用AopProxyFactory取得AopProxy物件,然後呼叫getProxy獲取得到代理物件;AopProxy是一個介面類,由兩個子類實現,一個是cglib2AopProxy,另一個是JdkDynamicProxy;至於要生成什麼樣的代理物件,所有資訊都封裝在AdvisedSupport中,ProxyFactoryBean本身也是AdvisedSupport的子類。ProxyCreatorSupport是AdvisedSupport的子類,上面的this,就是ProxyCreatorSupport,也是createAopProxy的引數;
這裡預設使用的是DefaultAopProxyFactory,這個AopProxyFactory,作為AopProxy的工廠物件,是在ProxyFactoryBean的基類ProxyCreatorSupport中被建立的,建立AopProxyFactory時,他被設定成了DefaultAopProxyFactory。
關於AopProxy代理物件的生成,如果目標物件時介面類,那麼適合使用JDK來生成代理物件,否則Spring會使用CGlib來生成代理物件。具體實現過程交給了JDKDynamicAopProxy和CglibProxyFactory的createCglibProxy方法;
在AopProxy代理物件的生成過程中,首先要從AdvisedSupport物件中取得配置的目標物件,這個目標物件是實現AOP的所必須的,因為我們是要對目標物件進行增強。目標物件檢查完成後,需要根據配置情況來決定使用什麼方式來建立AopProxy代理物件。預設使用Jdk生成AopProxy代理物件,
3.3 JDK生成AopProxy代理物件
從ProxyFactoryBean的getObject開始,裡面先對通知進行初始化,加入到攔截器中,然後判斷singleton還是prototype,分情況討論,如果是singleton,呼叫getSingletonInstance方法,這裡會得到代理物件介面的類物件targetClass;設定代理物件的介面資訊,然後呼叫getProxy(createAopProxy)方法,createAopProxy返回一個AopProxy物件,然後就會將ProxyFactoryBean物件作為AdvisedSupport引數傳入,然後進行生成代理物件,config中包含著target相關資訊和介面資訊。
JdkDynamicAopProxy本身實現了AopProxy、InvocationHandler和序列化介面,實現了invoke回撥方法,完成了代理物件和InvocationHandler即JdkDynamicAopProxy連線起來;
3.4 CGLIB生成AopProxy物件
CglibAopProxy類中的getProxy方法
4.Spring AOP攔截器呼叫的實現
JDK需要通過InvocationHandler來設定攔截器回撥,Cglib需要根據使用要求,通過DynamicAdvisedInterceptor來完成回撥
4.1JDKDynamicAopProxy的invoke攔截
Invoke方法中,核心內容是獲取攔截器鏈,如果攔截器鏈為空,直接呼叫目標方法,如果攔截器鏈不為空則先呼叫攔截器鏈,再對目標方法進行呼叫。
首先是獲得攔截器鏈,從List<Object> chain = this.advised.
getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);方法中,可以看出獲取interceptors的操作是由advised物件完成的,
4.2 cglibAopProxy的intercept攔截
與JDK的攔截類似,只是攔截器鏈的處理不一樣,這裡採用CglibMethodInvocation完成
4.3 目標物件方法的呼叫
如果沒有設定攔截器,會對目標物件的方法直接進行呼叫。使用AopUtils.invokeJoinpointUsingReflection的方法中實現的。
使用CglibAopProxy直接呼叫methodProxy.invoke,如果沒有攔截器就會呼叫父類的方法,即target的方法
4.4 AOP攔截器鏈的呼叫
不同的AopProxy代理物件,但最終對Aop攔截的處理基本類似:他們對攔截器鏈的呼叫都是在ReflectiveMethodInvocation中通過proceed方法實現的,cglib的CglibMethodInvocation是ReflectiveMethodInvocation的子類。Proceed中會逐個執行攔截器中的攔截方法。在執行攔截器的方法之前,需要對代理方法完成一個匹配判斷,通過這個匹配判斷來決定攔截器是否滿足切面要求。通過Pointcut的matches呼叫對方法進行匹配判斷,來決定是否需要進行增強。
Proceed方法中,先進行判斷,如果已經到攔截器末尾了,就直接呼叫目標物件的實現方法;否則沿著攔截器鏈繼續進行,得到下一個攔截器,通過這個攔截器進行matches判斷;判斷是否適用於增強的場合,如果是,從攔截器中得到interceptor,呼叫invoke方法進行增強,如果不匹配,那麼proceed會被遞迴呼叫,直到攔截器都被執行完了。
4.5 配置通知器
Proceed方法中得到了interceptorOrInterceptionAdvice攔截器,
interceptorOrInterceptionAdvice是獲得的攔截器,他通過攔截器機制對目標物件的行為增強起作用,這個攔截器來自interceptorsAndDynamicMethodMatchers,具體來說,他是interceptorsAndDynamicMethodMatchers持有的list中的一個元素;這個list是從哪裡獲得的,jdkDynamicAopProxy的invoke中
cglibAopProxy也有類似的,
獲取interceptors的操作是由advised物件來完成的,這個advise是一個AdvisedSupport物件,從類的繼承上來看,這個AdvisedSupport類同時也是ProxyFactoryBean的基類。從AdvisedSupport的程式碼中可以看到getInterceptorsAndDynamicInterceptionAdvice的實現,程式碼清單如下。在這個方法中獲得了攔截器鏈,在取得攔截器鏈的時候,為提高取得攔截器的效率,還為其設定了快取。
這裡使用了cache,利用cache獲取已有的interceptor鏈,但是第一次還是需要自己動手生成。這個interceptor鏈的生成是由advisorChainFactory完成的,這裡使用的是DefaultAdvisorChainFactory。在DefaultAdvisorChainFactory中實現了一個interceptor鏈的獲取過程,程式碼如下。
在這個獲取過程中,首先設定了一個list,其長度是由配置的通知器的個數來決定的,這個配置就是在XML中對ProxyFactoryBean做的interceptorNames屬性的配置。然後DefaultAdvisorChainFactory會通過一個AdvisorAdapterRegistry來實現攔截器的註冊,AdvisorAdapterRegistry對advice通知的織入功能起了很大的作用。有了AdvisorAdapterRegistry註冊器,利用它來對從ProxyFactoryBean配置中得到的通知進行適配,從而獲得相應的攔截器,再把他加入到前面設定好的List中去,完成所謂的攔截器註冊過程。
獲取到攔截器鏈之後,將目標物件,攔截器鏈,方法,代理物件等作為引數,建立一個ReflectiveMethodInvocation類,通過這個物件完成AOP功能實現的封裝;
ProxyFactoryBean的getObject方法裡面對advisor進行初始化,從xml配置中獲取advisor通知器。
Advisor通知器的取得是委託給IoC容器完成的,但是在ProxyFactoryBean如何獲的IoC容器,然後回撥IoC容器的getBean方法取得advisor的呢?初始化bean的過程中,對IoC容器在bean 中的回撥進行了設定,首先判斷這個bean型別是不是實現BeanFactoryAware介面,如果是,那麼它一定實現了BeanFactoryAware定義的介面方法,通過合格介面方法,可以把IoC容器設定到Bean自身定義的一個屬性中去。那麼在bean自身實現中,就能夠得到它所在的IoC容器,從而呼叫容器的getBean方法
4.6 Advice通知的實現
在DefaultAdvisorChainFactory中,這個工廠類負責生成攔截器鏈,在它的getInterceptorsAndDynamicInterceptionAdvice方法中,有一個適配和註冊過程,這個適配和註冊過程中,通過配置Spring預先設計好的攔截器,Spring加入它對AOp的實現,可以看到,首先構造一個GlobalAdvisorAdapterRegistry單例,然後對配置的Advisor通知器進行逐個遍歷,這些通知器鏈都是配置在interceptorNames中的,然後封裝在Advised型別的引數config中,可以方便的取得配置的通知器,有了這些通知器,接著就是一個由GlobalAdvisorAdapterRegistry來完成攔截器的適配和註冊過程。
GlobalAdvisorAdapterRegistry為AOP的實現做出了很大貢獻,getInterceptors方法封裝這advice織入實現的入口,先了解一下GlobalAdvisorAdapterRegistry單件,起到一個介面卡的作用,但同時它也是一個單件模式的應用,為Spring AOP模組提供了一個DefaultAdvisorAdapterRegistry單件,由它完成各種通知的適配和註冊工作。
在DefaultAdvisorAdapterRegistry中,設定了一系列的adapter介面卡,正是這些adapter的實現,為Spring aop的advice提供編織能力。含有與AOP對應的一系列通知相對應的adapter介面卡,這些主要體現在一下兩個方面:一是呼叫adapter的supportsAdvice方法,通過這個方法判斷取得的advice是什麼型別的advice通知;從而根據不同advice型別來註冊不同的adviceInterceptor,加入到interceptors的list中。另一方面,這些AdviceInterceptor都是AOP框架設計好了的,是為實現不同的advice功能提供服務的,有了這些AdviceInterceptor,實現了advice通知在AopProxy代理物件的織入功能。
介面卡模式,adaptee是一系列的advice
目標(Target):這就是所期待得到的介面。Interceptor攔截器
源(Adaptee)角色:現在需要適配的介面。Advise通知
介面卡(Adapter):介面卡類是本模式的核心。介面卡把源介面轉換成目標介面。顯然,這一角色不可以是介面,而必須是具體類。將對應的通知得到對應通知的攔截器
在DefaultAdvisorAdapterRegistry的getInterceptors呼叫中,將幾個AOP常用的adapter加入到adapter的list中,以MethodBeforeAdviceAdapter為例,實現了AdvisorAdapter的兩個介面方法:supportsAdvice,對advice型別進行判斷,如果advice是MethodBeforeAdvice的例項,那麼返回true;另一個對getInterceptor介面方法的實現,這個方法把advice通知從通知器中取出,建立一個MethodBeforeAdviceInterceptor物件,通過這個物件將advice通知包裝起來。
MethodBeforeAdviceInterceptor完成的是對MethodBeforeAdvice的封裝,可以在MethodBeforeAdviceInterceptor設計的invoke回撥方法中,看到首先出發了advice的before回撥,然後才是MethodInvocation的proceed方法呼叫.這裡就是和前面的ReflectiveMethodInvocation的proceed分析中聯絡起來了,從該類中的interceptorsAndDynamicMethodMatchers成員變數裡面封裝的都是interceptorsAndDynamicMethodMatcher物件,而物件中中封裝著Interceptor和MethodMatcher,因此dm.interceptor.invoke,就是呼叫對應的攔截器的invoke方法,最終會根據不同的advice型別,觸發spring對不同的advice的攔截器封裝,比如MethodBeforeAdvice,最終會觸發MethodBeforeAdviceInterceptor的invoke方法。在MethodBeforeAdviceInterceptor方法中,會先呼叫advice的before方法,這就是MethodBeforeAdvice所需要的對目標物件的增強效果:在方法呼叫之前完成通知增強。
4.7 ProxyFactory實現AOP
程式設計式使用ProxyFactory實現AOP
ProxyFactory實現AOP功能,其實現原理與ProxyFactoryBean的實現原理一致,只是在最外層的表現形式不同。ProxyFactory沒有使用FactoryBean的IoC封裝,而是通過繼承ProxyCreatorSupport的功能來完成aop的屬性配置。ProxyFactory的getProxy方法取得AopProxy代理物件,getProxy的實現使用了ProxyFactory基類ProxyCreatorSupport的createProxy方法來生成AopProxy代理物件,而AopProxy代理物件的生成是由AopProxyFactory來完成的,使用JDK或者CGLIB的代理物件。