(转)Spring AOP的底层实现技术
AOP概述
軟件的編程語(yǔ)言最終的目的就是用更自然更靈活的方式模擬世界,從原始機(jī)器語(yǔ)言到過(guò)程語(yǔ)言再到面向?qū)ο蟮恼Z(yǔ)言,我們看到編程語(yǔ)言在一步步用更自然、更強(qiáng)大的方式描述軟件。AOP是軟件開發(fā)思想的一個(gè)飛躍,AOP的引入將有效彌補(bǔ)OOP的不足,OOP和AOP分別從縱向和橫向?qū)浖M(jìn)行抽象,有效地消除重復(fù)性的代碼,使代碼以更優(yōu)雅的更有效的方式進(jìn)行邏輯表達(dá)。
AOP有三種植入切面的方法:其一是編譯期織入,這要求使用特殊的Java編譯器,AspectJ是其中的代表者;其二是類裝載期織入,而這要求使用特殊的類裝載器,AspectJ和AspectWerkz是其中的代表者;其三為動(dòng)態(tài)代理織入,在運(yùn)行期為目標(biāo)類添加增強(qiáng)生成子類的方式,Spring AOP采用動(dòng)態(tài)代理織入切面。
Spring AOP使用了兩種代理機(jī)制,一種是基于JDK的動(dòng)態(tài)代理,另一種是基于CGLib的動(dòng)態(tài)代理,之所以需要兩種代理機(jī)制,很大程度上是因?yàn)镴DK本身只提供基于接口的代理,不支持類的代理。
基于JDK的代理和基于CGLib的代理是Spring AOP的核心實(shí)現(xiàn)技術(shù),認(rèn)識(shí)這兩代理技術(shù),有助于探究Spring AOP的實(shí)現(xiàn)機(jī)理。只要你愿意,你甚至可以拋開Spring,提供自己的AOP實(shí)現(xiàn)。
帶有橫切邏輯的實(shí)例
????
首先,我們來(lái)看一個(gè)無(wú)法通過(guò)OOP進(jìn)行抽象的重復(fù)代碼邏輯,它們就是AOP改造的主要對(duì)象。下面,我們通過(guò)一個(gè)業(yè)務(wù)方法性能監(jiān)視的實(shí)例了解橫切邏輯。業(yè)務(wù)方法性能監(jiān)視,在每一個(gè)業(yè)務(wù)方法調(diào)用之前開始監(jiān)視,業(yè)務(wù)方法結(jié)束后結(jié)束監(jiān)視并給出性能報(bào)告:
代碼清單 2 ForumService:包含性能監(jiān)視橫切代碼
| package com.baobaotao.proxy; ?public void removeTopic(int topicId) ...{ ?public void removeForum(int forumId) ...{ |
代碼清單 2中粗體表示的代碼就是具有橫切特征的代碼,需要進(jìn)行性能監(jiān)視的每個(gè)業(yè)務(wù)方法的前后都需要添加類似的性能監(jiān)視語(yǔ)句。
????
我們保證實(shí)例的完整性,我們提供了一個(gè)非常簡(jiǎn)單的性能監(jiān)視實(shí)現(xiàn)類,如所示代碼清單 3所示:
代碼清單 3 PerformanceMonitor
| package com.baobaotao.proxy; public class PerformanceMonitor { |
PerformanceMonitor提供了兩個(gè)方法,begin(String method)方法開始對(duì)某個(gè)業(yè)務(wù)類方法的監(jiān)視,method為業(yè)務(wù)方法的簽名,而end()方法結(jié)束對(duì)業(yè)務(wù)方法的監(jiān)視,并給出性能監(jiān)視的信息。由于每一個(gè)業(yè)務(wù)方法都必須單獨(dú)記錄性能監(jiān)視數(shù)據(jù),所以我們使用了ThreadLocal,ThreadLocal是削除非線程安全狀態(tài)的不二法寶。ThreadLocal中的元素為方法性能記錄對(duì)象MethodPerformace,它的代碼如下所示:
代碼清單 4 MethodPerformace
| package com.baobaotao.proxy; |
#p#
通過(guò)下面代碼測(cè)試這個(gè)擁有方法性能監(jiān)視能力的業(yè)務(wù)方法:
| package com.baobaotao.proxy; |
我們得到以下的輸出信息:
| begin monitor... begin monitor... |
如實(shí)例所示,要對(duì)業(yè)務(wù)類進(jìn)行性能監(jiān)視,就必須在每個(gè)業(yè)務(wù)類方法的前后兩處添加上重復(fù)性的開啟性能監(jiān)視和結(jié)束性能監(jiān)視的代碼。這些非業(yè)務(wù)邏輯的性能監(jiān)視代碼破壞了作為業(yè)務(wù)類ForumServiceImpl的純粹性。下面,我們分別JDK動(dòng)態(tài)代理和CGLib動(dòng)態(tài)代理技術(shù),將業(yè)務(wù)方法中開啟和結(jié)束性能監(jiān)視的這些橫切代碼從業(yè)務(wù)類中完成移除。
JDK動(dòng)態(tài)代理
????
在JDK 1.3以后提供了動(dòng)態(tài)代理的技術(shù),允許開發(fā)者在運(yùn)行期創(chuàng)建接口的代理實(shí)例。在Sun剛推出動(dòng)態(tài)代理時(shí),還很難想象它有多大的實(shí)際用途,現(xiàn)在我們終于發(fā)現(xiàn)動(dòng)態(tài)代理是實(shí)現(xiàn)AOP的絕好底層技術(shù)。
????
JDK的動(dòng)態(tài)代理主要涉及到j(luò)ava.lang.reflect包中的兩個(gè)類:Proxy和InvocationHandler。其中InvocationHandler是一個(gè)接口,可以通過(guò)實(shí)現(xiàn)該接口定義橫切邏輯,在并通過(guò)反射機(jī)制調(diào)用目標(biāo)類的代碼,動(dòng)態(tài)將橫切邏輯和業(yè)務(wù)邏輯編織在一起。
???
而Proxy為InvocationHandler實(shí)現(xiàn)類動(dòng)態(tài)創(chuàng)建一個(gè)符合某一接口的代理實(shí)例。這樣講一定很抽象,我們馬上著手動(dòng)用Proxy和InvocationHandler這兩個(gè)魔法戒對(duì)上一節(jié)中的性能監(jiān)視代碼進(jìn)行AOP式的改造。
????
首先,我們從業(yè)務(wù)類ForumServiceImpl 中刪除性能監(jiān)視的橫切代碼,使ForumServiceImpl只負(fù)責(zé)具體的業(yè)務(wù)邏輯,如所示:
代碼清單 5 ForumServiceImpl:移除性能監(jiān)視橫切代碼
| package com.baobaotao.proxy; |
在代碼清單 5中的①和②處,原來(lái)的性能監(jiān)視代碼被移除了,我們只保留了真正的業(yè)務(wù)邏輯。
????
從業(yè)務(wù)類中移除的橫切代碼當(dāng)然還得找到一個(gè)寄居之所,InvocationHandler就是橫切代碼的家園樂(lè)土,我們將性能監(jiān)視的代碼安置在PerformaceHandler中,如代碼清單 6所示:
代碼清單 6 PerformaceHandler
| package com.baobaotao.proxy; public class PerformaceHandler implements InvocationHandler { |
粗體部分的代碼為性能監(jiān)視的橫切代碼,我們發(fā)現(xiàn),橫切代碼只出現(xiàn)一次,而不是原來(lái)那樣星灑各處。大家注意②處的method.invoke(),該語(yǔ)句通過(guò)反射的機(jī)制調(diào)用目標(biāo)對(duì)象的方法,這樣InvocationHandler的invoke(Object proxy, Method method, Object[] args)方法就將橫切代碼和目標(biāo)業(yè)務(wù)類代碼編織到一起了,所以我們可以將InvocationHandler看成是業(yè)務(wù)邏輯和橫切邏輯的編織器。下面,我們對(duì)這段代碼做進(jìn)一步的說(shuō)明。
#p#
首先,我們實(shí)現(xiàn)InvocationHandler接口,該接口定義了一個(gè) invoke(Object proxy, Method method, Object[] args)的方法,proxy是代理實(shí)例,一般不會(huì)用到;method是代理實(shí)例上的方法,通過(guò)它可以發(fā)起對(duì)目標(biāo)類的反射調(diào)用;args是通過(guò)代理類傳入的方法參數(shù),在反射調(diào)用時(shí)使用。
????
此外,我們?cè)跇?gòu)造函數(shù)里通過(guò)target傳入真實(shí)的目標(biāo)對(duì)象,如①處所示,在接口方法invoke(Object proxy, Method method, Object[] args)里,將目標(biāo)類實(shí)例傳給method.invoke()方法,通過(guò)反射調(diào)用目標(biāo)類方法,如②所示。
????
下面,我們通過(guò)Proxy結(jié)合PerformaceHandler創(chuàng)建ForumService接口的代理實(shí)例,如代碼清單 7所示:
代碼清單 7 TestForumService:創(chuàng)建代理實(shí)例
| package com.baobaotao.proxy; |
上面的代碼完成了業(yè)務(wù)類代碼和橫切代碼編織和接口代理實(shí)例生成的工作,其中在②處,我們將ForumService實(shí)例編織為一個(gè)包含性能監(jiān)視邏輯的PerformaceHandler實(shí)例,然后在③處,通過(guò)Proxy的靜態(tài)方法newProxyInstance()為融合了業(yè)務(wù)類邏輯和性能監(jiān)視邏輯的handler創(chuàng)建一個(gè)ForumService接口的代理實(shí)例,該方法的第一個(gè)入?yún)轭惣虞d器,第二個(gè)入?yún)閯?chuàng)建的代理實(shí)例所要實(shí)現(xiàn)的一組接口,第三個(gè)參數(shù)是整合了業(yè)務(wù)邏輯和橫切邏輯的編織器對(duì)象。
按照③處的設(shè)置方式,這個(gè)代理實(shí)例就實(shí)現(xiàn)了目標(biāo)業(yè)務(wù)類的所有接口,也即ForumServiceImpl的ForumService接口。這樣,我們就可以按照調(diào)用ForumService接口的實(shí)例相同的方式調(diào)用代理實(shí)例,如④所示。運(yùn)行以上的代碼,輸出以下的信息:
| begin monitor... begin monitor... |
我們發(fā)現(xiàn),程序的運(yùn)行效果和直接在業(yè)務(wù)類中編寫性能監(jiān)視邏輯的效果一致,但是在這里,原來(lái)分散的橫切邏輯代碼已經(jīng)被我們抽取到PerformaceHandler中。當(dāng)其它業(yè)務(wù)類(如UserService、SystemService等)的業(yè)務(wù)方法也需要使用性能監(jiān)視時(shí),我們只要按照以上的方式,分別為它們創(chuàng)建代理對(duì)象就可以了。下面,我們用時(shí)序圖描述調(diào)用關(guān)系,進(jìn)一步代理實(shí)例的本質(zhì),如圖1所示:
?
圖1:代理實(shí)例的時(shí)序圖
????
我們?cè)谏蠄D中特別使用虛線陰影的方式對(duì)通過(guò)代理器創(chuàng)建的ForumService實(shí)例進(jìn)行凸顯,該實(shí)例內(nèi)部利用PerformaceHandler整合橫切邏輯和業(yè)務(wù)邏輯。調(diào)用者調(diào)用代理對(duì)象的的removeForum()和removeTopic()方法時(shí),上圖的內(nèi)部調(diào)用時(shí)序清晰地告訴了我們實(shí)際上所發(fā)生的一切。
CGLib動(dòng)態(tài)代理
???
使用JDK創(chuàng)建代理有一個(gè)限制,即它只能為接口創(chuàng)建代理,這一點(diǎn)我們從Proxy的接口方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)就看得很清楚,第三個(gè)入?yún)nterfaces就是為代理實(shí)例指定的實(shí)現(xiàn)接口。雖然,面向接口的編程被很多很有影響力人(包括Rod Johnson)的推崇,但在實(shí)際開發(fā)中,開發(fā)者也遇到了很多困惑:難道對(duì)一個(gè)簡(jiǎn)單業(yè)務(wù)表的操作真的需要?jiǎng)?chuàng)建5個(gè)類(領(lǐng)域?qū)ο箢?、Dao接口,Dao實(shí)現(xiàn)類,Service接口和Service實(shí)現(xiàn)類)嗎?對(duì)于這一問(wèn)題,我們還是留待大家進(jìn)一步討論?,F(xiàn)在的問(wèn)題是:對(duì)于沒(méi)有通過(guò)接口定義業(yè)務(wù)方法的類,如何動(dòng)態(tài)創(chuàng)建代理實(shí)例呢?JDK的代理技術(shù)顯然已經(jīng)黔驢技窮,CGLib作為一個(gè)替代者,填補(bǔ)了這個(gè)空缺。你可以從http://cglib.sourceforge.net/獲取CGLib的類包,也可以直接從Spring的關(guān)聯(lián)類庫(kù)lib/cglib中獲取類包。
#p#
???
CGLib采用非常底層的字節(jié)碼技術(shù),可以為一個(gè)類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,并在攔截方法相應(yīng)地織入橫切邏輯。下面,我們采用CGLib技術(shù),編寫一個(gè)可以為任何類創(chuàng)建織入性能監(jiān)視橫切邏輯的代理對(duì)象的代理器,如代碼清單 8所示:
代碼清單 8 CglibProxy
| package com.baobaotao.proxy; public class CglibProxy implements MethodInterceptor { |
在上面代碼中,你可以通過(guò)getProxy(Class clazz)為一個(gè)類創(chuàng)建動(dòng)態(tài)代理對(duì)象,該代理對(duì)象是指定類clazz的子類。在這個(gè)代理對(duì)象中,我們織入性能監(jiān)視的橫切邏輯(粗體部分)。intercept(Object obj, Method method, Object[] args,MethodProxy proxy)是CGLib定義的Inerceptor接口的方法,obj表示父類的實(shí)例,method為父類方法的反射對(duì)象,args為方法的動(dòng)態(tài)入?yún)?#xff0c;而proxy為代理類實(shí)例。
????
下面,我們通過(guò)CglibProxy為ForumServiceImpl類創(chuàng)建代理對(duì)象,并測(cè)試代理對(duì)象的方法,如代碼清單 9所示:
代碼清單 9 TestForumService:測(cè)試Cglib創(chuàng)建的代理類
| package com.baobaotao.proxy; |
在①中,我們通過(guò)CglibProxy為ForumServiceImpl動(dòng)態(tài)創(chuàng)建了一個(gè)織入性能監(jiān)視邏輯的代理對(duì)象,并調(diào)用了代理對(duì)象的業(yè)務(wù)方法。運(yùn)行上面的代碼,輸入以下的信息:
| begin monitor... |
觀察以上的輸出,除了發(fā)現(xiàn)兩個(gè)業(yè)務(wù)方法中都織入了性能監(jiān)控的邏輯外,我們還發(fā)現(xiàn)代理類的名字是com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0,這個(gè)特殊的類就是CGLib為ForumServiceImpl所動(dòng)態(tài)創(chuàng)建的子類。
小結(jié)
??????
Spring AOP在底層就是利用JDK動(dòng)態(tài)代理或CGLib動(dòng)態(tài)代理技術(shù)為目標(biāo)Bean織入橫切邏輯。在這里,我們對(duì)以上兩節(jié)動(dòng)態(tài)創(chuàng)建代理對(duì)象做一個(gè)小結(jié)。
在PerformaceHandler和CglibProxy中,有三點(diǎn)值得注意的地方是:第一,目標(biāo)類的所有方法都被添加了性能監(jiān)視橫切的代碼,而有時(shí),這并不是我們所期望的,我們可能只希望對(duì)業(yè)務(wù)類中的某些方法織入橫切代碼;第二,我們手工指定了織入橫切代碼的織入點(diǎn),即在目標(biāo)類業(yè)務(wù)方法的開始和結(jié)束前調(diào)用;第三,我們手工編寫橫切代碼。以上三個(gè)問(wèn)題,在AOP中占用重要的地位,因?yàn)镾pring AOP的主要工作就是圍繞以上三點(diǎn)展開:Spring AOP通過(guò)Pointcut(切點(diǎn))指定在哪些類的哪些方法上施加橫切邏輯,通過(guò)Advice(增強(qiáng))描述橫切邏輯和方法的具體織入點(diǎn)(方法前、方法后、方法的兩端等),此外,Spring還通過(guò)Advisor(切面)組合Pointcut和Advice。有了Advisor的信息,Spring就可以利用JDK或CGLib的動(dòng)態(tài)代理技術(shù)為目標(biāo)Bean創(chuàng)建織入切面的代理對(duì)象了。
JDK動(dòng)態(tài)代理所創(chuàng)建的代理對(duì)象,在JDK 1.3下,性能強(qiáng)差人意。雖然在高版本的JDK中,動(dòng)態(tài)代理對(duì)象的性能得到了很大的提高,但是有研究表明,CGLib所創(chuàng)建的動(dòng)態(tài)代理對(duì)象的性能依舊比JDK的所創(chuàng)建的代理對(duì)象的性能高不少(大概10倍)。而CGLib在創(chuàng)建代理對(duì)象時(shí)性能卻比JDK動(dòng)態(tài)代理慢很多(大概8倍),所以對(duì)于singleton的代理對(duì)象或者具有實(shí)例池的代理,因?yàn)椴恍枰l繁創(chuàng)建代理對(duì)象,所以比較適合用CGLib動(dòng)態(tài)代理技術(shù),反之適合用JDK動(dòng)態(tài)代理技術(shù)。此外,由于CGLib采用生成子類的技術(shù)創(chuàng)建代理對(duì)象,所以不能對(duì)目標(biāo)類中的final方法進(jìn)行代理。
轉(zhuǎn)載于:https://www.cnblogs.com/m-xy/p/3658590.html
總結(jié)
以上是生活随笔為你收集整理的(转)Spring AOP的底层实现技术的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Asp.net下web.config或是
- 下一篇: 在JavaScript里写类层次结构?别