1. 程式人生 > >AOP--代理模式,攔截器的簡易實現及原理

AOP--代理模式,攔截器的簡易實現及原理

上文中提到代理分為靜態代理和動態代理,採用代理是為了通過不修改原始碼的情況下給程式動態統一新增功能,利用代理技術可以將業務邏輯中一些非業務邏輯的程式碼分離出來,把他們獨立到業務邏輯類外,比如日誌記錄,效能統計,安全控制,事務處理,異常處理等。這樣做,不僅降低了業務邏輯和非業務邏輯的耦合性,提高程式的可重用性,同時提高了開發的效率。

下面以新增日誌記錄為例,分析靜態代理的使用。建立一個使用者管理類UserManagerImpl,並建立新增使用者方法addUser,為其良好擴充套件性,建立一個通用介面UserManager,程式碼分別如下:

介面程式碼:

  1. package com.snail.pattern;  
  2. public interface UserManager {  
  3.     public void addUser(String userId,String userName);  
  4. }  

實現類程式碼:

  1. package com.snail.pattern;  
  2. public class UserManagerImpl implements UserManager {  
  3.     public void addUser(String userId, String userName) {  
  4.         try {  
  5.             //System.out.println("開始執行");  
  6.             System.out.println("HelloWorld!"
    );  
  7.             //System.out.println("執行成功!");  
  8.         }catch(Exception e) {  
  9.             e.printStackTrace();  
  10.             //System.out.println("執行失敗!");  
  11.             throw new RuntimeException();  
  12.         }     
  13.     }  
  14. }  

從程式碼可以看出,註釋裡面的日誌內容和業務邏輯毫無關係,無形中使耦合性增加,如果很多類中需要新增這些日誌程式碼,工作量不言而喻,修改起來也非常麻煩。如果採用靜態代理把列印日誌的程式碼抽取到代理類中,通過代理類和業務邏輯類繼承自同一個父類,客戶端直接呼叫代理類完成需求,這樣就解決了客戶端與業務邏輯類的耦合。示例程式碼如下:

  1. package com.snail.pattern;  
  2. public class UserManagerImplProxy implements UserManager{  
  3.     private UserManager userManager;  
  4.     public UserManagerImplProxy(UserManager userManager){  
  5.         this.userManager = userManager;  
  6.     }  
  7.     @Override  
  8.     public void addUser(String userId, String userName) {  
  9.         try {  
  10.             System.out.println("開始執行");  
  11.             userManager.addUser(userId, userName);  
  12.             System.out.println("執行成功!");  
  13.         }catch(Exception e) {  
  14.             e.printStackTrace();  
  15.             System.out.println("執行失敗!");  
  16.         }             
  17.     }  
  18. }  

客戶端呼叫程式碼如下:

  1. package com.snail.pattern;  
  2. public class Client {  
  3.     /** 
  4.      * @param args 
  5.      */  
  6.     public static void main(String[] args) {  
  7.         UserManager userManager = new UserManagerImplProxy(new UserManagerImpl());  
  8.         userManager.addUser("0111""張三");  
  9.     }  
  10. }  

靜態代理雖隔離了與業務邏輯無關的程式碼,降低了耦合,讓業務邏輯類更專注於業務邏輯,但無法減少程式碼量,系統重複程式碼過多,加大了程式設計師工作量。因此,JDK動態代理完美解決了此問題,動態代理支援在系統執行期給類動態新增代理,然後通過操控代理類完成對目標類的呼叫。

繼續演化上面舉的例子,將靜態代理改為動態代理,抽象類UserManager和目標類UserManagerImpl中的程式碼不變,將靜態代理類UserManagerImplProxy刪除,新增LoadHandler類,並讓它實現InvocationHandler介面中的invoke方法,程式碼如下:

  1. package com.snail.pattern;  
  2. import java.lang.reflect.InvocationHandler;  
  3. import java.lang.reflect.Method;  
  4. import java.lang.reflect.Proxy;  
  5. public class LogHandler implements InvocationHandler {  
  6.     //保留一份targetObject目標類物件  
  7.     private Object targetObject;  
  8.     //Proxy類動態建立一份目標代理類  
  9.     public Object newProxyInstance(Object targetObject){  
  10.         this.targetObject = targetObject;  
  11.         return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);   
  12.     }  
  13.     @Override  
  14.     public Object invoke(Object proxy, Method method, Object[] args)  
  15.             throws Throwable {  
  16.         System.out.println("開始執行!");  
  17.         for(int i=0;i<args.length;i++){  
  18.             System.out.println(args[i]);  
  19.         }  
  20.         Object ret = null;  
  21.         try{  
  22.             //呼叫目標方法  
  23.             ret = method.invoke(targetObject, args);  
  24.             System.out.println("執行成功!");  
  25.         }catch(Exception e){  
  26.             e.printStackTrace();  
  27.             System.out.println("執行失敗!");  
  28.             throw e;  
  29.         }  
  30.         return ret;  
  31.     }  
  32. }  

Proxy類所建立的目標類必須實現至少一個介面,在呼叫newProxyInstance方法時必須與目標類的類載入器和介面一致;invoke方法非常類似Filter中的doFilter方法,它將呼叫目標類的所有方法在未到達UserManagerImpl之前截獲,根據我們自己的需求進行預處理後,繼續呼叫UserManagerImpl。

為了保持invoke方法的通用性,目標方法中的引數以陣列args形式傳遞,如果方法中有返回值,則返回,沒有返回值,則返回null。如此一來,程式設計師不必為每個目標類設計一個代理類,所有需要列印日誌的類都可以共用這個LogHandler,如果不想使用日誌功能就可以直接刪除LogHandler類,對原功能沒有絲毫影響,如同揭去顯示器上的保護膜,不會影響顯示器的使用一般。

客戶端呼叫程式碼如下:

  1. package com.snail.pattern;  
  2. public class Client {  
  3.     /** 
  4.      * @param args 
  5.      */  
  6.     public static void main(String[] args) {  
  7.         LogHandler logHandler = new LogHandler();  
  8.         UserManager userManager = (UserManager)logHandler.newProxyInstance(new UserManagerImpl());  
  9.         userManager.addUser("id""name");  
  10.     }  
  11. }  

通過以上兩篇博文我相信大家對AOP的原理和應用場合會有一知半解,文章中難免會出現錯誤,還望大家不吝賜教。

轉自:http://blog.csdn.net/bjyfb/article/details/7350256