面試題:靜態代理和動態代理的區別和聯系 沒用


代理Proxy

 

Proxy代理模式是一種結構型設計模式,主要解決的問題是:在直接訪問對象時帶來的問題

 

代理是一種常用的設計模式,其目的就是為其他對象提供一個代理以控制對某個對象的訪問。代理類負責為委托類預處理消息,過濾消息並轉發消息,以及進行消息被委托類執行后的后續處理。

 

                      2

 

 

 

為了保持行為的一致性,代理類和委托類通常會實現相同的接口,所以在訪問者看來兩者沒有絲毫的區別。通過代理類這中間一層,能有效控制對委托類對象的直接訪問,也可以很好地隱藏和保護委托類對象,同時也為實施不同控制策略預留了空間,從而在設計上獲得了更大的靈活性。

 

更通俗的說,代理解決的問題當兩個類需要通信時,引入第三方代理類,將兩個類的關系解耦,讓我們只了解代理類即可,而且代理的出現還可以讓我們完成與另一個類之間的關系的統一管理,但是切記,代理類和委托類要實現相同的接口,因為代理真正調用的還是委托類的方法。

 

使用場合舉例:

如果需要委托類處理某一業務,那么我們就可以先在代理類中統一處理然后在調用具體實現類

 

按照代理的創建時期,代理類可以分為兩種: 

靜態:由程序員創建代理類或特定工具自動生成源代碼再對其編譯。在程序運行前代理類的.class文件就已經存在了。

動態:在程序運行時運用反射機制動態創建而成。

 

下面分別用靜態代理與動態代理演示一個示例:

添加打印日志的功能,即每個方法調用之前和調用之后寫入日志

 

靜態代理:

 

具體用戶管理實現類

 

[java]  view plain  copy
 
 print?
  1. public class UserManagerImpl implements UserManager {  
  2.   
  3.     @Override  
  4.     public void addUser(String userId, String userName) {  
  5.         System.out.println("UserManagerImpl.addUser");  
  6.     }  
  7.   
  8.     @Override  
  9.     public void delUser(String userId) {  
  10.         System.out.println("UserManagerImpl.delUser");  
  11.     }  
  12.   
  13.     @Override  
  14.     public String findUser(String userId) {  
  15.         System.out.println("UserManagerImpl.findUser");  
  16.         return "張三";  
  17.     }  
  18.   
  19.     @Override  
  20.     public void modifyUser(String userId, String userName) {  
  21.         System.out.println("UserManagerImpl.modifyUser");  
  22.   
  23.     }  
  24. }  

 

代理類--代理用戶管理實現類

 

[java]  view plain  copy
 
 print?
  1. public class UserManagerImplProxy implements UserManager {  
  2.   
  3.     // 目標對象  
  4.     private UserManager userManager;  
  5.     // 通過構造方法傳入目標對象  
  6.     public UserManagerImplProxy(UserManager userManager){  
  7.         this.userManager=userManager;  
  8.     }  
  9.     @Override  
  10.     public void addUser(String userId, String userName) {  
  11.         try{  
  12.                 //添加打印日志的功能  
  13.                 //開始添加用戶  
  14.                 System.out.println("start-->addUser()");  
  15.                 userManager.addUser(userId, userName);  
  16.                 //添加用戶成功  
  17.                 System.out.println("success-->addUser()");  
  18.             }catch(Exception e){  
  19.                 //添加用戶失敗  
  20.                 System.out.println("error-->addUser()");  
  21.             }  
  22.     }  
  23.   
  24.     @Override  
  25.     public void delUser(String userId) {  
  26.         userManager.delUser(userId);  
  27.     }  
  28.   
  29.     @Override  
  30.     public String findUser(String userId) {  
  31.         userManager.findUser(userId);  
  32.         return "張三";  
  33.     }  
  34.   
  35.     @Override  
  36.     public void modifyUser(String userId, String userName) {  
  37.         userManager.modifyUser(userId,userName);  
  38.     }  
  39.   
  40. }  

 

客戶端調用

 

[java]  view plain  copy
 
 print?
  1. public class Client {  
  2.   
  3.     public static void main(String[] args){  
  4.         //UserManager userManager=new UserManagerImpl();  
  5.         UserManager userManager=new UserManagerImplProxy(new UserManagerImpl());  
  6.         userManager.addUser("1111", "張三");  
  7.     }  
  8. }  

 

靜態代理類優缺點

 

優點:

 

代理使客戶端不需要知道實現類是什么,怎么做的,而客戶端只需知道代理即可(解耦合),對於如上的客戶端代碼,newUserManagerImpl()可以應用工廠將它隱藏,如上只是舉個例子而已。

 

缺點:

1代理類和委托類實現了相同的接口,代理類通過委托類實現了相同的方法。這樣就出現了大量的代碼重復。如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。

2)代理對象只服務於一種類型的對象,如果要服務多類型的對象。勢必要為每一種對象都進行代理,靜態代理在程序規模稍大時就無法勝任了。如上的代碼是只為UserManager類的訪問提供了代理,但是如果還要為其他類如Department類提供代理的話,就需要我們再次添加代理Department的代理類。

 

舉例說明:代理可以對實現類進行統一的管理,如在調用具體實現類之前,需要打印日志等信息,這樣我們只需要添加一個代理類,在代理類中添加打印日志的功能,然后調用實現類,這樣就避免了修改具體實現類。滿足我們所說的開閉原則。但是如果想讓每個實現類都添加打印日志的功能的話,就需要添加多個代理類,以及代理類中各個方法都需要添加打印日志功能(如上的代理方法中刪除,修改,以及查詢都需要添加上打印日志的功能)

即靜態代理類只能為特定的接口(Service)服務。如想要為多個接口服務則需要建立很多個代理類。

 

引入動態代理:

 

根據如上的介紹,你會發現每個代理類只能為一個接口服務,這樣程序開發中必然會產生許多的代理類

所以我們就會想辦法可以通過一個代理類完成全部的代理功能,那么我們就需要用動態代理

 

在上面的示例中,一個代理只能代理一種類型,而且是在編譯器就已經確定被代理的對象。而動態代理是在運行時,通過反射機制實現動態代理,並且能夠代理各種類型的對象

 

Java中要想實現動態代理機制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 類的支持

 

java.lang.reflect.InvocationHandler接口的定義如下:

 

[java]  view plain  copy
 
 print?
  1. //Object proxy:被代理的對象  
  2. //Method method:要調用的方法  
  3. //Object[] args:方法調用時所需要參數  
  4. public interface InvocationHandler {  
  5.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;  
  6. }  

 

java.lang.reflect.Proxy類的定義如下:

 

[java]  view plain  copy
 
 print?
  1. //CLassLoader loader:類的加載器  
  2. //Class<?> interfaces:得到全部的接口  
  3. //InvocationHandler h:得到InvocationHandler接口的子類的實例  
  4. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException  

 

 

 

動態代理:

 

具體實現類

 

[java]  view plain  copy
 
 print?
  1. public class UserManagerImpl implements UserManager {  
  2.   
  3.     @Override  
  4.     public void addUser(String userId, String userName) {  
  5.         System.out.println("UserManagerImpl.addUser");  
  6.     }  
  7.   
  8.     @Override  
  9.     public void delUser(String userId) {  
  10.         System.out.println("UserManagerImpl.delUser");  
  11.     }  
  12.   
  13.     @Override  
  14.     public String findUser(String userId) {  
  15.         System.out.println("UserManagerImpl.findUser");  
  16.         return "張三";  
  17.     }  
  18.   
  19.     @Override  
  20.     public void modifyUser(String userId, String userName) {  
  21.         System.out.println("UserManagerImpl.modifyUser");  
  22.   
  23.     }  
  24.   
  25. }  

 

 

動態創建代理對象的類

 

[java]  view plain  copy
 
 print?
  1. //動態代理類只能代理接口(不支持抽象類),代理類都需要實現InvocationHandler類,實現invoke方法。該invoke方法就是調用被代理接口的所有方法時需要調用的,該invoke方法返回的值是被代理接口的一個實現類  
  2.      
  3. public class LogHandler implements InvocationHandler {  
  4.   
  5.     // 目標對象  
  6.     private Object targetObject;  
  7.     //綁定關系,也就是關聯到哪個接口(與具體的實現類綁定)的哪些方法將被調用時,執行invoke方法。              
  8.     public Object newProxyInstance(Object targetObject){  
  9.         this.targetObject=targetObject;  
  10.         //該方法用於為指定類裝載器、一組接口及調用處理器生成動態代理類實例    
  11.         //第一個參數指定產生代理對象的類加載器,需要將其指定為和目標對象同一個類加載器  
  12.         //第二個參數要實現和目標對象一樣的接口,所以只需要拿到目標對象的實現接口  
  13.         //第三個參數表明這些被攔截的方法在被攔截時需要執行哪個InvocationHandler的invoke方法  
  14.         //根據傳入的目標返回一個代理對象  
  15.         return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),  
  16.                 targetObject.getClass().getInterfaces(),this);  
  17.     }  
  18.     @Override  
  19.     //關聯的這個實現類的方法被調用時將被執行  
  20.     /*InvocationHandler接口的方法,proxy表示代理,method表示原對象被調用的方法,args表示方法的參數*/  
  21.     public Object invoke(Object proxy, Method method, Object[] args)  
  22.             throws Throwable {  
  23.         System.out.println("start-->>");  
  24.         for(int i=0;i<args.length;i++){  
  25.             System.out.println(args[i]);  
  26.         }  
  27.         Object ret=null;  
  28.         try{  
  29.             /*原對象方法調用前處理日志信息*/  
  30.             System.out.println("satrt-->>");  
  31.               
  32.             //調用目標方法  
  33.             ret=method.invoke(targetObject, args);  
  34.             /*原對象方法調用后處理日志信息*/  
  35.             System.out.println("success-->>");  
  36.         }catch(Exception e){  
  37.             e.printStackTrace();  
  38.             System.out.println("error-->>");  
  39.             throw e;  
  40.         }  
  41.         return ret;  
  42.     }  
  43.   
  44. }  

 

 

被代理對象targetObject通過參數傳遞進來,我們通過targetObject.getClass().getClassLoader()獲取ClassLoader對象,然后通過targetObject.getClass().getInterfaces()獲取它實現的所有接口,然后將targetObject包裝到實現了InvocationHandler接口的LogHandler對象中。通過newProxyInstance函數我們就獲得了一個動態代理對象。

 

客戶端代碼

[java]  view plain  copy
 
 print?
  1. public class Client {  
  2.   
  3.     public static void main(String[] args){  
  4.         LogHandler logHandler=new LogHandler();  
  5.         UserManager userManager=(UserManager)logHandler.newProxyInstance(new UserManagerImpl());  
  6.         //UserManager userManager=new UserManagerImpl();  
  7.         userManager.addUser("1111", "張三");  
  8.     }  
  9. }  

 

 

可以看到,我們可以通過LogHandler代理不同類型的對象,如果我們把對外的接口都通過動態代理來實現,那么所有的函數調用最終都會經過invoke函數的轉發,因此我們就可以在這里做一些自己想做的操作,比如日志系統、事務、攔截器、權限控制等。這也就是AOP(面向切面編程)的基本原理。

 

插曲:

 

AOP(AspectOrientedProgramming):將日志記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中划分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的代碼---解耦。

 

針對如上的示例解釋:

 

我們來看上面的UserManagerImplProxy類,它的兩個方法System.out.println("start-->addUser()")和System.out.println("success-->addUser()"),這是做核心動作之前和之后的兩個截取段,正是這兩個截取段,卻是我們AOP的基礎,在OOP里,System.out.println("start-->addUser()")、核心動作、System.out.println("success-->addUser()")這個三個動作在多個類里始終在一起,但他們所要完成的邏輯卻是不同的,如System.out.println("start-->addUser()")里做的可能是權限的判斷,在所有類中它都是做權限判斷,而在每個類里核心動作卻各不相同,System.out.println("success-->addUser()")可能做的是日志,在所有類里它都做日志。正是因為在所有的類里,核心代碼之前的操作和核心代碼之后的操作都做的是同樣的邏輯,因此我們需要將它們提取出來,單獨分析,設計和編碼,這就是我們的AOP思想。一句話說,AOP只是在對OOP的基礎上進行進一步抽象,使我們的類的職責更加單一。

 

動態代理優點:

 

動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。而且動態代理的應用使我們的類職責更加單一,復用性更強

 

總結:

 

其實所謂代理,就是一個人或者一個機構代表另一個人或者另一個機構采取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之前起到中介的作用。

 

代理對象就是把被代理對象包裝一層,在其內部做一些額外的工作,比如用戶需要上facebook,而普通網絡無法直接訪問,網絡代理幫助用戶先FQ,然后再訪問facebook。這就是代理的作用了。

 

縱觀靜態代理與動態代理,它們都能實現相同的功能,而我們看從靜態代理到動態代理的這個過程,我們會發現其實動態代理只是對類做了進一步抽象和封裝,使其復用性和易用性得到進一步提升而這不僅僅符合了面向對象的設計理念,其中還有AOP的身影,這也提供給我們對類抽象的一種參考。關於動態代理與AOP的關系,個人覺得AOP是一種思想,而動態代理是一種AOP思想的實現!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM