JDK動態代理
JDK動態代理
在JDK 1.3以後提供了動態代理的技術,允許開發人員在運行期建立介面的代理執行個體。在Sun剛推出動態代理時,還很難想象它有多大的實際用途,現在我們終於發現動態代理是實現AOP的絕好底層技術。
JDK的動態代理主要涉及到java.lang.reflect包中的兩個類:Proxy和InvocationHandler。其中InvocationHandler是一個介面,可以通過實現該介面定義橫切邏輯,在並通過反射機制調用目標類的代碼,動態將橫切邏輯和商務邏輯編織在一起。
而Proxy為InvocationHandler實作類別動態建立一個符合某一介面的代理執行個體。這樣講一定很抽象,我們馬上著手動用Proxy和InvocationHandler這兩個魔法戒對上一節中的效能監控代碼進行AOP式的改造。
首先,我們從業務類ForumServiceImpl 中刪除效能監控的橫切代碼,使ForumServiceImpl只負責具體的商務邏輯,如所示:
代碼清單 5 ForumServiceImpl:移除效能監控橫切代碼
package com.baobaotao.proxy;public class ForumServiceImpl implements ForumService { public void removeTopic(int topicId) { ① System.out.println("類比刪除Topic記錄:"+topicId); try { Thread.currentThread().sleep(20); } catch (Exception e) { throw new RuntimeException(e); } ② } public void removeForum(int forumId) { ① System.out.println("類比刪除Forum記錄:"+forumId); try { Thread.currentThread().sleep(40); } catch (Exception e) { throw new RuntimeException(e); } ② }}
在代碼清單 5中的①和②處,原來的效能監控代碼被移除了,我們只保留了真正的商務邏輯。
從業務類中移除的橫切代碼當然還得找到一個寄居之所,InvocationHandler就是橫切代碼的家園樂土,我們將效能監控的代碼安置在PerformaceHandler中,如代碼清單 6所示:
代碼清單 6 PerformaceHandler
package com.baobaotao.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class PerformaceHandler implements InvocationHandler { private Object target; public PerformaceHandler(Object target){//①target為目標的業務類 this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName()); Object bj = method.invoke(target, args);//②通過反射方法調用目標業務類的業務方法 PerformanceMonitor.end(); return obj; }}
粗體部分的代碼為效能監控的橫切代碼,我們發現,橫切代碼只出現一次,而不是原來那樣星灑各處。大家注意②處的method.invoke(),該語句通過反射的機制調用目標對象的方法,這樣InvocationHandler的invoke(Object proxy, Method method, Object[] args)方法就將橫切代碼和目標業務類代碼編織到一起了,所以我們可以將InvocationHandler看成是商務邏輯和橫切邏輯的編織器。下面,我們對這段代碼做進一步的說明。
首先,我們實現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:建立代理執行個體
package com.baobaotao.proxy;import java.lang.reflect.Proxy;public class TestForumService { public static void main(String[] args) { ForumService target = new ForumServiceImpl();//①目標業務類//② 將目標業務類和橫切代碼編織到一起 PerformaceHandler handler = new PerformaceHandler(target);//③為編織了目標業務類邏輯和效能監控橫切邏輯的handler建立代理類 ForumService proxy = (ForumService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);//④ 操作代理執行個體 proxy.removeForum(10); proxy.removeTopic(1012); }}
上面的程式碼完成了業務類代碼和橫切代碼編織和介面代理執行個體產生的工作,其中在②處,我們將ForumService執行個體編織為一個包含效能監控邏輯的PerformaceHandler執行個體,然後在③處,通過Proxy的靜態方法newProxyInstance()為融合了業務類邏輯和效能監控邏輯的handler建立一個ForumService介面的代理執行個體,該方法的第一個入參為類載入器,第二個入參為建立的代理執行個體所要實現的一組介面,第三個參數是整合了商務邏輯和橫切邏輯的編織器對象。
按照③處的設定方式,這個代理執行個體就實現了目標業務類的所有介面,也即ForumServiceImpl的ForumService介面。這樣,我們就可以按照調用ForumService介面的執行個體相同的方式調用代理執行個體,如④所示。運行以上的代碼,輸出以下的資訊:
begin monitor...類比刪除Forum記錄:10end monitor...com.baobaotao.proxy.ForumServiceImpl.removeForum花費47毫秒。begin monitor...類比刪除Topic記錄:1012end monitor...com.baobaotao.proxy.ForumServiceImpl.removeTopic花費26毫秒。
我們發現,程式的運行效果和直接在業務類中編寫效能監控邏輯的效果一致,但是在這裡,原來分散的橫切邏輯代碼已經被我們抽取到PerformaceHandler中。當其它業務類(如UserService、SystemService等)的業務方法也需要使用效能監控時,我們只要按照以上的方式,分別為它們建立代理對象就可以了。下面,我們用時序圖描述調用關係,進一步代理執行個體的本質,1所示:
圖 1代理執行個體的時序圖
我們在中特別使用虛線陰影的方式對通過代理器建立的ForumService執行個體進行凸顯,該執行個體內部利用PerformaceHandler整合橫切邏輯和商務邏輯。調用者調用代理對象的的removeForum()和removeTopic()方法時,的內部調用時序清晰地告訴了我們實際上所發生的一切。