jdk proxy invocationhandler (jdk動態代理)

葉威1發表於2013-07-24

JDK動態代理
    在JDK 1.3以後提供了動態代理的技術,允許開發者在執行期建立介面的代理例項。在Sun剛推出動態代理時,還很難想象它有多大的實際用途,現在我們終於發現動態代理是實現AOP的絕好底層技術。
    JDK的動態代理主要涉及到java.lang.reflect包中的兩個類:Proxy和InvocationHandler。其中InvocationHandler是一個介面,可以通過實現該介面定義橫切邏輯,在並通過反射機制呼叫目標類的程式碼,動態將橫切邏輯和業務邏輯編織在一起。
   而Proxy為InvocationHandler實現類動態建立一個符合某一介面的代理例項。這樣講一定很抽象,我們馬上著手動用Proxy和InvocationHandler這兩個魔法戒對上一節中的效能監視程式碼進行AOP式的改造。
    首先,我們從業務類ForumServiceImpl 中刪除效能監視的橫切程式碼,使ForumServiceImpl只負責具體的業務邏輯,如所示:
程式碼清單 5 ForumServiceImpl:移除效能監視橫切程式碼

  1. package com.baobaotao.proxy;  
  2.   
  3. public class ForumServiceImpl implements ForumService {  
  4.   
  5.  public void removeTopic(int topicId) {  
  6.          ①  
  7.   System.out.println("模擬刪除Topic記錄:"+topicId);  
  8.   try {  
  9.    Thread.currentThread().sleep(20);  
  10.   } catch (Exception e) {  
  11.    throw new RuntimeException(e);  
  12.   }  
  13.    ②  
  14.  }  
  15.  public void removeForum(int forumId) {  
  16.          ①  
  17.   System.out.println("模擬刪除Forum記錄:"+forumId);  
  18.   try {  
  19.    Thread.currentThread().sleep(40);  
  20.   } catch (Exception e) {  
  21.    throw new RuntimeException(e);  
  22.   }  
  23.          ②  
  24.  }  
  25. }  

 

在程式碼清單 5中的①和②處,原來的效能監視程式碼被移除了,我們只保留了真正的業務邏輯。
    從業務類中移除的橫切程式碼當然還得找到一個寄居之所,InvocationHandler就是橫切程式碼的家園樂土,我們將效能監視的程式碼安置在PerformaceHandler中,如程式碼清單 6所示:
程式碼清單 6 PerformaceHandler

  1. package com.baobaotao.proxy;  
  2. import java.lang.reflect.InvocationHandler;  
  3. import java.lang.reflect.Method;  
  4.   
  5. public class PerformaceHandler implements InvocationHandler {  
  6.     private Object target;  
  7.  public PerformaceHandler(Object target){//①target為目標的業務類  
  8.   this.target = target;  
  9.  }  
  10.  public Object invoke(Object proxy, Method method, Object[] args)  
  11.    throws Throwable {  
  12.   PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName());  
  13.   Object bj = method.invoke(target, args);//②通過反射方法呼叫目標業務類的業務方法  
  14.   PerformanceMonitor.end();  
  15.   return obj;  
  16.  }  
  17. }  

 

 粗體部分的程式碼為效能監視的橫切程式碼,我們發現,橫切程式碼只出現一次,而不是原來那樣星灑各處。大家注意②處的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:建立代理例項

  1. package com.baobaotao.proxy;  
  2. import java.lang.reflect.Proxy;  
  3. public class TestForumService {  
  4.  public static void main(String[] args) {  
  5.   ForumService target = new ForumServiceImpl();//①目標業務類  
  6. //② 將目標業務類和橫切程式碼編織到一起  
  7.   PerformaceHandler handler = new PerformaceHandler(target);  
  8.          //③為編織了目標業務類邏輯和效能監視橫切邏輯的handler建立代理類  
  9.   ForumService proxy = (ForumService) Proxy.newProxyInstance(  
  10. target.getClass().getClassLoader(),  
  11.     target.getClass().getInterfaces(),  
  12.  handler);  
  13.          //④ 操作代理例項  
  14.   proxy.removeForum(10);  
  15.   proxy.removeTopic(1012);  
  16.  }  
  17. }  

 

上面的程式碼完成了業務類程式碼和橫切程式碼編織和介面代理例項生成的工作,其中在②處,我們將ForumService例項編織為一個包含效能監視邏輯的PerformaceHandler例項,然後在③處,通過Proxy的靜態方法newProxyInstance()為融合了業務類邏輯和效能監視邏輯的handler建立一個ForumService介面的代理例項,該方法的第一個入參為類載入器,第二個入參為建立的代理例項所要實現的一組介面,第三個引數是整合了業務邏輯和橫切邏輯的編織器物件。
按照③處的設定方式,這個代理例項就實現了目標業務類的所有介面,也即ForumServiceImpl的ForumService介面。這樣,我們就可以按照呼叫ForumService介面的例項相同的方式呼叫代理例項,如④所示。執行以上的程式碼,輸出以下的資訊:

  1. begin monitor...  
  2. 模擬刪除Forum記錄:10  
  3. end monitor...  
  4. com.baobaotao.proxy.ForumServiceImpl.removeForum花費47毫秒。  
  5.   
  6. begin monitor...  
  7. 模擬刪除Topic記錄:1012  
  8. end monitor...  
  9. com.baobaotao.proxy.ForumServiceImpl.removeTopic花費26毫秒。  

 

 我們發現,程式的執行效果和直接在業務類中編寫效能監視邏輯的效果一致,但是在這裡,原來分散的橫切邏輯程式碼已經被我們抽取到PerformaceHandler中。當其它業務類(如UserService、SystemService等)的業務方法也需要使用效能監視時,我們只要按照以上的方式,分別為它們建立代理物件就可以了。下面,我們用時序圖描述呼叫關係,進一步代理例項的本質,如圖1所示:
 
                                圖 1代理例項的時序圖
    我們在上圖中特別使用虛線陰影的方式對通過代理器建立的ForumService例項進行凸顯,該例項內部利用PerformaceHandler整合橫切邏輯和業務邏輯。呼叫者呼叫代理物件的的removeForum()和removeTopic()方法時,上圖的內部呼叫時序清晰地告訴了我們實際上所發生的一切。

相關文章