Spring AOP的底層實現技術
AOP概述
軟件的編程語言最終的目的就是用更自然更靈活的方式模擬世界,從原始機器語言到過程語言再到面向對象的語言,我們看到編程語言在一步步用更自然、更強大的方式描述軟件。AOP是軟件開發思想的一個飛躍,AOP的引入將有效彌補OOP的不足,OOP和AOP分別從縱向和橫向對軟件進行抽象,有效地消除重復性的代碼,使代碼以更優雅的更有效的方式進行邏輯表達。
AOP有三種植入切面的方法:其一是編譯期織入,這要求使用特殊的Java編譯器,AspectJ是其中的代表者;其二是類裝載期織入,而這要求使用特殊的類裝載器,AspectJ和AspectWerkz是其中的代表者;其三為動態代理織入,在運行期為目標類添加增強生成子類的方式,Spring AOP采用動態代理織入切面。
Spring AOP使用了兩種代理機制,一種是基于JDK的動態代理,另一種是基于CGLib的動態代理,之所以需要兩種代理機制,很大程度上是因為JDK本身只提供基于接口的代理,不支持類的代理。
基于JDK的代理和基于CGLib的代理是Spring AOP的核心實現技術,認識這兩代理技術,有助于探究Spring AOP的實現機理。只要你愿意,你甚至可以拋開Spring,提供自己的AOP實現。
帶有橫切邏輯的實例
???
首先,我們來看一個無法通過OOP進行抽象的重復代碼邏輯,它們就是AOP改造的主要對象。下面,我們通過一個業務方法性能監視的實例了解橫切邏輯。業務方法性能監視,在每一個業務方法調用之前開始監視,業務方法結束后結束監視并給出性能報告:
代碼清單 2 ForumService:包含性能監視橫切代碼
|
代碼清單 2中粗體表示的代碼就是具有橫切特征的代碼,需要進行性能監視的每個業務方法的前后都需要添加類似的性能監視語句。
???
我們保證實例的完整性,我們提供了一個非常簡單的性能監視實現類,如所示代碼清單 3所示:
代碼清單 3 PerformanceMonitor
|
PerformanceMonitor提供了兩個方法,begin(String method)方法開始對某個業務類方法的監視,method為業務方法的簽名,而end()方法結束對業務方法的監視,并給出性能監視的信息。由于每一個業務方法都必須單獨記錄性能監視數據,所以我們使用了ThreadLocal,ThreadLocal是削除非線程安全狀態的不二法寶。ThreadLocal中的元素為方法性能記錄對象MethodPerformace,它的代碼如下所示:
代碼清單 4 MethodPerformace
|
#p#
通過下面代碼測試這個擁有方法性能監視能力的業務方法:
|
我們得到以下的輸出信息:
|
如實例所示,要對業務類進行性能監視,就必須在每個業務類方法的前后兩處添加上重復性的開啟性能監視和結束性能監視的代碼。這些非業務邏輯的性能監視代碼破壞了作為業務類ForumServiceImpl的純粹性。下面,我們分別JDK動態代理和CGLib動態代理技術,將業務方法中開啟和結束性能監視的這些橫切代碼從業務類中完成移除。
JDK動態代理
???
在JDK 1.3以后提供了動態代理的技術,允許開發者在運行期創建接口的代理實例。在Sun剛推出動態代理時,還很難想象它有多大的實際用途,現在我們終于發現動態代理是實現AOP的絕好底層技術。
???
JDK的動態代理主要涉及到java.lang.reflect包中的兩個類:Proxy和InvocationHandler。其中InvocationHandler是一個接口,可以通過實現該接口定義橫切邏輯,在并通過反射機制調用目標類的代碼,動態將橫切邏輯和業務邏輯編織在一起。
??
而Proxy為InvocationHandler實現類動態創建一個符合某一接口的代理實例。這樣講一定很抽象,我們馬上著手動用Proxy和InvocationHandler這兩個魔法戒對上一節中的性能監視代碼進行AOP式的改造。
???
首先,我們從業務類ForumServiceImpl 中刪除性能監視的橫切代碼,使ForumServiceImpl只負責具體的業務邏輯,如所示:
代碼清單 5 ForumServiceImpl:移除性能監視橫切代碼
|
在代碼清單 5中的①和②處,原來的性能監視代碼被移除了,我們只保留了真正的業務邏輯。
???
從業務類中移除的橫切代碼當然還得找到一個寄居之所,InvocationHandler就是橫切代碼的家園樂土,我們將性能監視的代碼安置在PerformaceHandler中,如代碼清單 6所示:
代碼清單 6 PerformaceHandler
|
粗體部分的代碼為性能監視的橫切代碼,我們發現,橫切代碼只出現一次,而不是原來那樣星灑各處。大家注意②處的method.invoke(),該語句通過反射的機制調用目標對象的方法,這樣InvocationHandler的invoke(Object proxy, Method method, Object[] args)方法就將橫切代碼和目標業務類代碼編織到一起了,所以我們可以將InvocationHandler看成是業務邏輯和橫切邏輯的編織器。下面,我們對這段代碼做進一步的說明。
#p#
首先,我們實現InvocationHandler接口,該接口定義了一個 invoke(Object proxy, Method method, Object[] args)的方法,proxy是代理實例,一般不會用到;method是代理實例上的方法,通過它可以發起對目標類的反射調用;args是通過代理類傳入的方法參數,在反射調用時使用。
???
此外,我們在構造函數里通過target傳入真實的目標對象,如①處所示,在接口方法invoke(Object proxy, Method method, Object[] args)里,將目標類實例傳給method.invoke()方法,通過反射調用目標類方法,如②所示。
???
下面,我們通過Proxy結合PerformaceHandler創建ForumService接口的代理實例,如代碼清單 7所示:
代碼清單 7 TestForumService:創建代理實例
|
上面的代碼完成了業務類代碼和橫切代碼編織和接口代理實例生成的工作,其中在②處,我們將ForumService實例編織為一個包含性能監視邏輯的PerformaceHandler實例,然后在③處,通過Proxy的靜態方法newProxyInstance()為融合了業務類邏輯和性能監視邏輯的handler創建一個ForumService接口的代理實例,該方法的第一個入參為類加載器,第二個入參為創建的代理實例所要實現的一組接口,第三個參數是整合了業務邏輯和橫切邏輯的編織器對象。
按照③處的設置方式,這個代理實例就實現了目標業務類的所有接口,也即ForumServiceImpl的ForumService接口。這樣,我們就可以按照調用ForumService接口的實例相同的方式調用代理實例,如④所示。運行以上的代碼,輸出以下的信息:
|
我們發現,程序的運行效果和直接在業務類中編寫性能監視邏輯的效果一致,但是在這里,原來分散的橫切邏輯代碼已經被我們抽取到PerformaceHandler中。當其它業務類(如UserService、SystemService等)的業務方法也需要使用性能監視時,我們只要按照以上的方式,分別為它們創建代理對象就可以了。下面,我們用時序圖描述調用關系,進一步代理實例的本質,如圖1所示:
?
圖1:代理實例的時序圖
???
我們在上圖中特別使用虛線陰影的方式對通過代理器創建的ForumService實例進行凸顯,該實例內部利用PerformaceHandler整合橫切邏輯和業務邏輯。調用者調用代理對象的的removeForum()和removeTopic()方法時,上圖的內部調用時序清晰地告訴了我們實際上所發生的一切。
CGLib動態代理
??
使用JDK創建代理有一個限制,即它只能為接口創建代理,這一點我們從Proxy的接口方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)就看得很清楚,第三個入參interfaces就是為代理實例指定的實現接口。雖然,面向接口的編程被很多很有影響力人(包括Rod Johnson)的推崇,但在實際開發中,開發者也遇到了很多困惑:難道對一個簡單業務表的操作真的需要創建5個類(領域對象類、Dao接口,Dao實現類,Service接口和Service實現類)嗎?對于這一問題,我們還是留待大家進一步討論。現在的問題是:對于沒有通過接口定義業務方法的類,如何動態創建代理實例呢?JDK的代理技術顯然已經黔驢技窮,CGLib作為一個替代者,填補了這個空缺。你可以從http://cglib.sourceforge.net/獲取CGLib的類包,也可以直接從Spring的關聯類庫lib/cglib中獲取類包。
#p#
??
CGLib采用非常底層的字節碼技術,可以為一個類創建子類,并在子類中采用方法攔截的技術攔截所有父類方法的調用,并在攔截方法相應地織入橫切邏輯。下面,我們采用CGLib技術,編寫一個可以為任何類創建織入性能監視橫切邏輯的代理對象的代理器,如代碼清單 8所示:
代碼清單 8 CglibProxy
|
在上面代碼中,你可以通過getProxy(Class clazz)為一個類創建動態代理對象,該代理對象是指定類clazz的子類。在這個代理對象中,我們織入性能監視的橫切邏輯(粗體部分)。intercept(Object obj, Method method, Object[] args,MethodProxy proxy)是CGLib定義的Inerceptor接口的方法,obj表示父類的實例,method為父類方法的反射對象,args為方法的動態入參,而proxy為代理類實例。
???
下面,我們通過CglibProxy為ForumServiceImpl類創建代理對象,并測試代理對象的方法,如代碼清單 9所示:
代碼清單 9 TestForumService:測試Cglib創建的代理類
|
在①中,我們通過CglibProxy為ForumServiceImpl動態創建了一個織入性能監視邏輯的代理對象,并調用了代理對象的業務方法。運行上面的代碼,輸入以下的信息:
|
觀察以上的輸出,除了發現兩個業務方法中都織入了性能監控的邏輯外,我們還發現代理類的名字是com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0,這個特殊的類就是CGLib為ForumServiceImpl所動態創建的子類。
小結
?????
Spring AOP在底層就是利用JDK動態代理或CGLib動態代理技術為目標Bean織入橫切邏輯。在這里,我們對以上兩節動態創建代理對象做一個小結。
在PerformaceHandler和CglibProxy中,有三點值得注意的地方是:第一,目標類的所有方法都被添加了性能監視橫切的代碼,而有時,這并不是我們所期望的,我們可能只希望對業務類中的某些方法織入橫切代碼;第二,我們手工指定了織入橫切代碼的織入點,即在目標類業務方法的開始和結束前調用;第三,我們手工編寫橫切代碼。以上三個問題,在AOP中占用重要的地位,因為Spring AOP的主要工作就是圍繞以上三點展開:Spring AOP通過Pointcut(切點)指定在哪些類的哪些方法上施加橫切邏輯,通過Advice(增強)描述橫切邏輯和方法的具體織入點(方法前、方法后、方法的兩端等),此外,Spring還通過Advisor(切面)組合Pointcut和Advice。有了Advisor的信息,Spring就可以利用JDK或CGLib的動態代理技術為目標Bean創建織入切面的代理對象了。
JDK動態代理所創建的代理對象,在JDK 1.3下,性能強差人意。雖然在高版本的JDK中,動態代理對象的性能得到了很大的提高,但是有研究表明,CGLib所創建的動態代理對象的性能依舊比JDK的所創建的代理對象的性能高不少(大概10倍)。而CGLib在創建代理對象時性能卻比JDK動態代理慢很多(大概8倍),所以對于singleton的代理對象或者具有實例池的代理,因為不需要頻繁創建代理對象,所以比較適合用CGLib動態代理技術,反之適合用JDK動態代理技術。此外,由于CGLib采用生成子類的技術創建代理對象,所以不能對目標類中的final方法進行代理。
posted on 2010-08-20 11:38 tobyxiong 閱讀(432) 評論(0) 編輯 收藏 所屬分類: java