設計模式之動態代理-proxy

         動態代理作爲代理模式的一種擴展形式,廣泛應用於框架(尤其是基於AOP的框架)的設計與開發,本文將通過實例來講解Java動態代理的實現過程。

       友情提示:本文略有難度,讀者需具備代理模式相關基礎知識微笑

 

        通常情況下,代理模式中的每一個代理類在編譯之後都會生成一個class文件,代理類所實現的接口和所代理的方法都被固定,這種代理被稱之爲靜態代理(Static Proxy)。那麼有沒有一種機制能夠讓系統在運行時動態創建代理類?答案就是本文將要介紹的動態代理(Dynamic Proxy)。動態代理是一種較爲高級的代理模式,它在事務管理、AOPAspect-OrientedProgramming,面向方面編程)等領域都發揮了重要的作用。

 

      在傳統的代理模式中,客戶端通過Proxy類調用RealSubject類的request()方法,同時還可以在代理類中封裝其他方法(如preRequest()postRequest()等)。如果按照這種方法使用代理模式,那麼代理類和真實主題類都應該是事先已經存在的,代理類的接口和所代理方法都已明確指定,如果需要爲不同的真實主題類提供代理類或者代理一個真實主題類中的不同方法,都需要增加新的代理類,這將導致系統中的類個數急劇增加,因此需要想辦法減少系統中類的個數。動態代理可以讓系統能夠根據實際需要來動態創建代理類,讓同一個代理類能夠代理多個不同的真實主題類而且可以代理不同的方法。

 

       從JDK 1.3開始,Java語言提供了對動態代理的支持,Java語言實現動態代理時需要用到位於java.lang.reflect包中的一些類,現簡要說明如下:

 

      (1) Proxy

      Proxy類提供了用於創建動態代理類和實例對象的方法,它是所創建的動態代理類的父類,它最常用的方法如下:

  • public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces):該方法用於返回一個Class類型的代理類,在參數中需要提供類加載器並需要指定代理的接口數組(與真實主題類的接口列表一致)。
  • public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler h):該方法用於返回一個動態創建的代理類的實例,方法中第一個參數loader表示代理類的類加載器,第二個參數interfaces表示代理類所實現的接口列表(與真實主題類的接口列表一致),第三個參數h表示所指派的調用處理程序類。

      (2) InvocationHandler接口

      InvocationHandler接口是代理處理程序類的實現接口,該接口作爲代理實例的調用處理者的公共父類,每一個代理類的實例都可以提供一個相關的具體調用處理者(InvocationHandler接口的子類)。在該接口中聲明瞭如下方法:

  • public Object invoke(Objectproxy, Method method, Object[] args):該方法用於處理對代理類實例的方法調用並返回相應的結果,當一個代理實例中的業務方法被調用時將自動調用該方法。invoke()方法包含三個參數,其中第一個參數proxy表示代理類的實例,第二個參數method表示需要代理的方法,第三個參數args表示代理方法的參數數組。

        動態代理類需要在運行時指定所代理真實主題類的接口,客戶端在調用動態代理對象的方法時,調用請求會將請求自動轉發給InvocationHandler對象的invoke()方法,由invoke()方法來實現對請求的統一處理。

 

      下面通過一個簡單實例來學習如何使用動態代理模式:

       Sunny軟件公司欲爲公司OA系統數據訪問層DAO增加方法調用日誌,記錄每一個方法被調用的時間和調用結果,現使用動態代理進行設計和實現。

 

      本實例完整代碼如下所示:

  1. import java.lang.reflect.Proxy;  
  2. import java.lang.reflect.InvocationHandler;  
  3. import java.lang.reflect.InvocationTargetException;  
  4. import java.lang.reflect.Method;  
  5. import java.util.Calendar;  
  6. import java.util.GregorianCalendar;  
  7.   
  8. //抽象UserDAO:抽象主題角色  
  9. interface AbstractUserDAO {  
  10.     public Boolean findUserById(String userId);  
  11. }  
  12.   
  13. //抽象DocumentDAO:抽象主題角色  
  14. interface AbstractDocumentDAO {  
  15.     public Boolean deleteDocumentById(String documentId);  
  16. }  
  17.   
  18. //具體UserDAO類:真實主題角色  
  19. class UserDAO implements AbstractUserDAO {  
  20.     public Boolean findUserById(String userId) {  
  21.         if (userId.equalsIgnoreCase("張無忌")) {  
  22.             System.out.println("查詢ID爲" + userId + "的用戶信息成功!");  
  23.             return true;  
  24.         }  
  25.         else {  
  26.             System.out.println("查詢ID爲" + userId + "的用戶信息失敗!");  
  27.             return false;  
  28.         }  
  29.     }  
  30. }  
  31.   
  32. //具體DocumentDAO類:真實主題角色  
  33. class DocumentDAO implements AbstractDocumentDAO {  
  34.     public Boolean deleteDocumentById(String documentId) {  
  35.         if (documentId.equalsIgnoreCase("D001")) {  
  36.             System.out.println("刪除ID爲" + documentId + "的文檔信息成功!");  
  37.             return true;  
  38.         }  
  39.         else {  
  40.             System.out.println("刪除ID爲" + documentId + "的文檔信息失敗!");  
  41.             return false;  
  42.         }  
  43.     }  
  44. }  
  45.   
  46. //自定義請求處理程序類  
  47. class DAOLogHandler implements InvocationHandler {  
  48.     private Calendar calendar;  
  49.     private Object object;  
  50.       
  51.     public DAOLogHandler() {      
  52.     }  
  53.       
  54.     //自定義有參構造函數,用於注入一個需要提供代理的真實主題對象  
  55.     public DAOLogHandler(Object object) {  
  56.         this.object = object;  
  57.     }  
  58.       
  59.     //實現invoke()方法,調用在真實主題類中定義的方法  
  60.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  61.         beforeInvoke();  
  62.         Object result = method.invoke(object, args); //轉發調用  
  63.         afterInvoke();  
  64.         return null;  
  65.     }  
  66.   
  67.     //記錄方法調用時間  
  68.     public void beforeInvoke(){  
  69.         calendar = new GregorianCalendar();  
  70.         int hour = calendar.get(Calendar.HOUR_OF_DAY);  
  71.         int minute = calendar.get(Calendar.MINUTE);  
  72.         int second = calendar.get(Calendar.SECOND);  
  73.         String time = hour + ":" + minute + ":" + second;  
  74.         System.out.println("調用時間:" + time);  
  75.     }  
  76.   
  77.     public void afterInvoke(){  
  78.         System.out.println("方法調用結束!" );  
  79.     }  
  80. }  

      編寫如下客戶端測試代碼:

  1. class Client {  
  2.     public static void main(String args[]) {  
  3.         InvocationHandler handler = null;  
  4.           
  5.         AbstractUserDAO userDAO = new UserDAO();  
  6.         handler = new DAOLogHandler(userDAO);  
  7.         AbstractUserDAO proxy = null;  
  8.         //動態創建代理對象,用於代理一個AbstractUserDAO類型的真實主題對象  
  9.         proxy = (AbstractUserDAO)Proxy.newProxyInstance(AbstractUserDAO. class.getClassLoader(), new Class[]{AbstractUserDAO.class}, handler);  
  10.         proxy.findUserById("張無忌"); //調用代理對象的業務方法  
  11.       
  12.         System.out.println("------------------------------");  
  13.       
  14.         AbstractDocumentDAO docDAO = new DocumentDAO();  
  15.         handler = new DAOLogHandler(docDAO);  
  16.         AbstractDocumentDAO proxy_new = null;  
  17. //動態創建代理對象,用於代理一個AbstractDocumentDAO類型的真實主題對象  
  18.         proxy_new = (AbstractDocumentDAO)Proxy.newProxyInstance(Abstract DocumentDAO.class.getClassLoader(), new Class[]{AbstractDocumentDAO.class}, handler);  
  19.         proxy_new.deleteDocumentById("D002"); //調用代理對象的業務方法  
  20.     }   
  21. }  

      編譯並運行程序,輸出結果如下:

調用時間:13:47:14

查詢ID爲張無忌的用戶信息成功!

方法調用結束!

------------------------------

調用時間:13:47:14

刪除IDD002的文檔信息失敗!

方法調用結束!

       通過使用動態代理,我們可以實現對多個真實主題類的統一代理和集中控制。


       注:JDK中提供的動態代理只能代理一個或多個接口,如果需要動態代理具體類或抽象類,可以使用CGLib(Code Generation Library)等工具,CGLib是一個功能較爲強大、性能和質量也較好的代碼生成包,在許多AOP框架中都得以廣泛應用,大家可以自行查閱相關資料來學習CGLib。

轉自點擊打開鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章