1. 程式人生 > >設計模式——代理(Proxy)

設計模式——代理(Proxy)

代理模式是一種結構型模式。代理模式給某一物件提供一個代理物件,並且由代理物件控制對原物件的引用。

#代理模式的結構

所謂代理就是由一個代理物件去代替處理目標物件,而處理的邏輯是由代理物件引用並呼叫目標物件進行處理的,也就是最終仍然是目標物件處理相應的邏輯,代理物件僅僅起到了中介作用。

代理模式結構圖:

這裡寫圖片描述

在代理模式中的角色:

  • 抽象物件角色:聲明瞭目標物件和代理物件的共同介面,這樣一來在任何可以使用目標物件的地方都可以使用代理物件。(利用介面,公開申明瞭需求特性,目標物件和代理物件均實現了這一介面,滿足這一需求特性,那麼客戶端認為他們均滿足條件,均可使用,亦可替代)
  • 目標物件角色:定義了代理物件所代表的目標物件。(核心程式碼物件)
  • 代理物件角色:代理物件內部含有目標物件的引用,從而可以在任何時候操作目標物件;代理物件提供一個與目標物件相同的介面,以便可以在任何時候替代目標物件。代理物件通常在客戶端呼叫傳遞給目標物件之前或之後,執行某個操作,而不是單純地將呼叫傳遞給目標物件。

#使用場景:

在實際開發過程中,代理類的實現比上述程式碼要複雜很多,代理模式根據其目的和實現方式不同可分為很多種類,其中常用的幾種代理模式簡要說明如下:

  • (1) 遠端代理(Remote Proxy):為一個位於不同的地址空間的物件提供一個本地的代理物件,這個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中,遠端代理又稱為大使(Ambassador)。
  • (2) 虛擬代理(Virtual Proxy):如果需要建立一個資源消耗較大的物件,先建立一個消耗相對較小的物件來表示,真實物件只在需要時才會被真正建立。
  • (3) 保護代理(Protect Proxy):控制對一個物件的訪問,可以給不同的使用者提供不同級別的使用許可權。
  • (4) 緩衝代理(Cache Proxy):為某一個目標操作的結果提供臨時的儲存空間,以便多個客戶端可以共享這些結果。
  • (5) 智慧引用代理(Smart Reference Proxy):當一個物件被引用時,提供一些額外的操作,例如將物件被呼叫的次數記錄下來等。
    在這些常用的代理模式中,有些代理類的設計非常複雜,例如遠端代理類,它封裝了底層網路通訊和對遠端物件的呼叫,其實現較為複雜。

#靜態代理

所謂靜態代理就是手動編寫代理類。在大規模代理很多物件的時候並不方便。

#動態代理

相對應的,動態代理就是自動生成代理類。

這點非常神奇,因為是在我們執行的時候,動態生成了一個類,並且該類又編譯載入進編譯器,可以生成該類的物件使用了,達到這點說明Java是動態語言了。

使用場景:

  • 日誌
  • 許可權控制
  • Transition
  • 方法前後做一些特殊固定操作
  • ….

#Java 動態代理類Proxy

Proxy中生成介面的實現類程式碼(native層實現):

private static native Class<?> generateProxy(String name, Class<?>[] interfaces,ClassLoader loader, Method[] methods,Class<?>[][] exceptions);

Proxy中利用反射建立物件:

return getProxyClass(loader, interfaces).getConstructor(InvocationHandler.class).newInstance(invocationHandler);

Retrifit2中的動態代理使用:

return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });

缺點:Java的動態代理類Proxy必須要讓目標類實現介面,如果沒有實現相應介面就無法使用Proxy創建出目標類的代理類物件。這時候就需要藉助CGLib建立動態代理類了。

#CGLib(位元組碼技術)

CGLib可以不需要目標類實現相應介面,一個普通類就可以給其動態生成代理類了。