1. 程式人生 > >【讀書筆記】大話設計模式—代理模式

【讀書筆記】大話設計模式—代理模式

代理模式(使用頻率:4顆星)

      代理模式(Proxy):為其他物件提供一個代理以控制對這個物件的訪問。

代理模式:給某一個物件提供一個代理或佔位符,並由代理物件來控制對原物件的訪問。

Proxy Pattern: Provide a surrogate or placeholder for another object to control access to it.

       代理模式是一種物件結構型模式。在代理模式中引入了一個新的代理物件,代理物件在客戶端物件和目標物件之間起到中介的作用,它去掉客戶不能看到的內容和服務或者增添客戶需要的額外的新服務。 常見的代理形式包括遠端代理、保護代理、虛擬代理、緩衝代理、智慧引用代理等

      代理模式的結構比較簡單,其核心是代理類,為了讓客戶端能夠一致性地對待真實物件和代理物件,在代理模式中引入了抽象層,代理模式結構如圖15-2所示:


15-2 代理模式結構圖

       由圖15-2可知,代理模式包含如下三個角色:

      (1) Subject(抽象主題角色):它聲明瞭真實主題和代理主題的共同介面,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行程式設計。

       (2) Proxy(代理主題角色):它包含了對真實主題的引用,從而可以在任何時候操作真實主題物件;在代理主題角色中提供一個與真實主題角色相同的介面,以便在任何時候都可以替代真實主題;代理主題角色還可以控制對真實主題的使用,負責在需要的時候建立和刪除真實主題物件,並對真實主題物件的使用加以約束。通常,在代理主題角色中,客戶端在呼叫所引用的真實主題操作之前或之後還需要執行其他操作,而不僅僅是單純呼叫真實主題物件中的操作。

       (3) RealSubject(真實主題角色):它定義了代理角色所代表的真實物件,在真實主題角色中實現了真實的業務操作,客戶端可以通過代理主題角色間接呼叫真實主題角色中定義的操作。

常用代理模式:靜態代理+動態代理

代理模式又稱作委託模式,許多其他模式(例如訪問者模式、策略模式、狀態模式)本質上實在更特殊的場合採用了代理模式

代理模式分為靜態代理動態代理

(1)靜態代理

所謂靜態,就是在程式執行前就已經存在代理類的位元組碼檔案,代理類和委託類的關係在執行前就確定了

靜態代理類的優點是,業務類只需要關注業務邏輯本身,保證了業務類的重用性

  1. //委託類和代理類共同的介面
  2. public interfaceMoveable{  
  3.       void move();  
  4. }  
  5. //記錄坦克執行時間的代理類
  6. publicclass TanktimeProxy impelments Moveable {  
  7.       private Moveable t;  //代理類持有委託類的物件
  8.       public TanktimeProxy(Moveable t){  
  9.              super();  
  10.              this.t = t;  
  11.       }  
  12.       publicvoid move(){  
  13.              longtime1 = System.currentTimeMillis();  
  14.              t.move();  
  15.              long time2 =System.currentTimeMillis();  
  16.              System.outl.println("坦克執行時間:"+(time2-time1));  
  17.       }  
  18. }  
  19. //記錄坦克執行日誌的代理類
  20. publicclass TanklogProxy impelments Moveable {  
  21.       private Moveable t;  //代理類持有委託類的物件
  22.       public TanklogProxy (Moveable t){  
  23.              super();  
  24.              this.t = t;  
  25.       }  
  26.       publicvoid move(){  
  27.              System.out.println("坦克開始移動");  
  28.              t.move();  
  29.              long time2 =System.currentTimeMillis();  
  30.              System.out.println("坦克停止移動");  
  31.       }  
  32. }  
  33. //委託類
  34. publicclass Tank implements Moveable{  
  35.       publicvoid move(){  
  36.              System.out.println("坦克移動……");  
  37.       }  
  38. }  
  39. //測試類
  40. Tank t = newTank();  
  41. Moveable move1 =new TanktimeProxy(t);  
  42. Moveable move2 =new TanklogProxy (move1);  
  43. move2.move();  

這樣就通過代理在Tank的move方法前後加入了日誌和時間統計功能

(2)動態代理

與動態代理相關的Java API:

java.lang.reflect.Proxy:這是java動態代理機制生成的所有動態代理類的父類,它提供了一組靜態方法來作為一組介面動態的生成代理類及其物件

//該方法用於獲取指定代理物件所關聯的呼叫處理器

staticInvocationHandler getInvocationHandler(Objectproxy)

//該方法用於獲取關聯於指定類裝載器和一組介面的動態代理類的類物件

static Class getProxyClass(ClassLoader loader,Class[] interfaces)

//該方法用於判斷指定類物件是否是一個動態代理類

static boolean isProxyClass(Class cl)

//該方法用於為指定類裝載器、一組介面和呼叫處理器生成動態代理類例項

static Object newProxyInstance(ClassLoader loader,Class [] interfaces, InvocationHandler h)

重點:

java.lang.reflect.InvocationHandler:這是呼叫處理器介面,它定義了一個invoke方法,用於集中處理在動態代理類物件上的方法呼叫,通常在該方法中實現對委託類的代理訪問

//第一個引數是代理例項,第二個引數是被呼叫的方法,第三個引數是呼叫方法時用到的引數

//呼叫處理器根據這三個引數進行預處理或分派到委託例項上反射執行

Object invoke(Object proxy, Method method,Object [] args)

java.lang.reflect.ClassLoader:類裝載器,負責將類的位元組碼裝載到java虛擬機器中併為其定義類物件,然後該類才能被呼叫

動態代理其實就是java.lang.reflect.Proxy類動態的根據指定的所有介面生成一個class byte,該class會繼承Proxy類,並實現所有你指定的介面(在引數中傳入的介面陣列);然後再利用指定的classloader將 class byte載入進系統,最後生成這樣一個類的物件,並初始化該物件的一些值,如invocationHandler,以即所有的介面對應的Method成員。初始化之後將物件返回給呼叫的客戶端。這樣客戶端拿到的就是一個實現你所有的介面的Proxy物件。請看例項分析:

  1. //業務介面類
  2. publicinterface BusinessProcessor {  
  3.  publicvoid processBusiness();  
  4. }  
  5.   //業務實現類
  6. publicclass BusinessProcessorImpl implements BusinessProcessor {  
  7.  publicvoid processBusiness() {  
  8.   System.out.println("processingbusiness.....");  
  9.  }  
  10. }  
  11. //業務代理類
  12. import java.lang.reflect.InvocationHandler;  
  13. import java.lang.reflect.Method;  
  14. publicclass BusinessProcessorHandler implements InvocationHandler{  
  15.  private Object target = null;  
  16.  BusinessProcessorHandler(Object target){  
  17.   this.target = target;  
  18.  }  
  19.  public Object invoke(Object proxy, Methodmethod, Object[] args)  
  20.    throws Throwable {  
  21.   System.out.println("You can dosomething here before process your business");  
  22.   Object result = method.invoke(target, args);  
  23.   System.out.println("You can dosomething here after process your business");  
  24.   return result;  
  25.  }  
  26. }  
  27. //客戶端應用類
  28. import java.lang.reflect.Field;  
  29. import java.lang.reflect.Method;  
  30. import java.lang.reflect.Modifier;  
  31. import java.lang.reflect.Proxy;  
  32. publicclass Test {  
  33.  publicstaticvoid main(String[] args) {  
  34.   BusinessProcessorImpl bpimpl = newBusinessProcessorImpl();  
  35.   BusinessProcessorHandler handler = newBusinessProcessorHandler(bpimpl);  
  36.   BusinessProcessor bp =(BusinessProcessor)Proxy.newProxyInstance(bpimpl.getClass().getClassLoader(),bpimpl.getClass().getInterfaces(), handler);  
  37.   bp.processBusiness();  
  38.  }  
  39. }  

列印結果:

You can dosomething here before process your business

processingbusiness.....

You can dosomething here after process your business

通過結果我們就能夠很簡單的看出Proxy的作用了,它能夠在你的核心業務方法前後做一些你所想做的輔助工作,如log日誌,安全機制等等。

Proxy.newProxyInstance方法會做如下幾件事:

1,根據傳入的第二個引數interfaces動態生成一個類,實現interfaces中的介面,該例中即BusinessProcessor介面的processBusiness方法。並且繼承了Proxy類,重寫了hashcode,toString,equals等三個方法。

2,通過傳入的第一個引數classloder將剛生成的類載入到jvm中。即將$Proxy0類load

3,利用第三個引數,呼叫$Proxy0的$Proxy0(InvocationHandler)建構函式 建立$Proxy0的物件,並且用interfaces引數遍歷其所有介面的方法,並生成Method物件初始化物件的幾個Method成員變數

4,將$Proxy0的例項返回給客戶端。

再看客戶端怎麼調

1,客戶端拿到的是$Proxy0的例項物件,由於$Proxy0繼承了BusinessProcessor,因此轉化為BusinessProcessor沒任何問題。

BusinessProcessorbp = (BusinessProcessor)Proxy.newProxyInstance(....);

2,bp.processBusiness();

實際上呼叫的是$Proxy0.processBusiness();那麼$Proxy0.processBusiness()的實現就是通過InvocationHandler去呼叫invoke方法