Java 基礎(十九)代理

diamond_lin發表於2017-11-27

代理模式

二十三種經典的設計模式之一,屬於一種結構模式。

二十三種設計模式不會單獨開篇去講,有時間會彙總寫一篇文章去接單介紹二十三種模式,然後根據“建立模式”、“行為模式”、“結構模式”三種型別去對比二十三中設計的優缺點,喜歡我的文章的小夥伴點個關注唄~

定義:為其他物件提供一種代理以控制對這個物件的訪問。在某些情況下,一個物件不適合或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介左右。

優點

  • 職責清晰,真實的角色就是實現實際的業務邏輯,不用關係其他非本職責的食物,通過後期的代理完成一件事物,附帶的結果就是程式設計簡潔清晰。
  • 代理物件可以在客戶端和目標之間起到中介的作用,這樣起到了中介的作用和保護了目標物件的作用。
  • 高擴充套件性

模式結構

一個真正的你要訪問的物件(目標類),一個代理物件,真正物件與代理物件實現同一個介面,先訪問代理類再訪問真正要訪問的物件。

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

靜態代理是由程式設計師建立或工具生成代理類的原始碼,再編譯代理類。所謂靜態也就是程式執行之前就已經存在代理類的位元組碼檔案,代理類和委託類的關係在執行前就確定了。

動態代理是在現實階段不用關心代理類,而在執行階段才指定哪一個物件。

靜態代理

剛剛我們說了,靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現共同介面或者繼承相同父類。
其實說白了就是為了控制物件引用,生活場景中,我們以買車為例,如果我們要買一輛車必須通過汽車4S 店,汽車4S 店就是充當代理角色,其目的是物流控制買車客戶的買車行為,必須通過汽車4S 店才能從騎車廠商買一輛車。

1.首先買車是一種行為,客戶和代理廠商都需要實現的介面

public interface IBuyCar{
    void buyCar();
}複製程式碼

2.宣告一個要買車的客戶,實現買車的介面

public class Customer implement IBuyCar{
    public int cash;//預算買車款
    public String name;//名字

    public Customer(int cash,String name){
        this.cash = cash;
        this.name = name
    }

    @Override
    public void buyCar(){
        System.out.println(name+"買了一輛車花了"+cash);
    }

}複製程式碼

3.宣告一個買車代理騎車4S 店,同樣實現買車介面,必須接受客戶下單。

public class PorscheProxy implement IBuyCar{
    public Customer customer;//

    public PorscheProxy(Customer customer){
        this.customer = customer;
    }

    @Override
    public void buyCar(){
        customer.buyCar();
    }

}複製程式碼

4.建立一個客戶端,模擬一次買車

public static void main(String[]args){
    Customer c = new Customer(10000,"ZhangSan");
    PorscheProxy proxy = new PorscheProxy(customer);
    proxy.buyCar();
}複製程式碼

OK,成功的使用代理模式完成了一次購車操作。

有什麼用?

然後,可能有同學會問了,你這個程式碼優在那裡?難道客戶就不能自己去買車麼?當然可以,如果在使用場景中實現類能滿足要求時,我們當然可以直接實現類,但當實現類不能滿足要求,要擴充套件需求,根據開閉原則,你又不能修改實現類程式碼,這時你就用代理類。比如買車的時候,需要對客戶進行一個購車款稽核,如果符合條件就買,不符合就不能買。

@Override
public void buyCar() {//實現為客戶買車
    if(customer.cash < 1000000){
       System.out.println("buyCar","你的錢不夠買一輛Porsche,建議去看看大眾");
       return;
    }
    customer.buyCar();
}複製程式碼

優缺點

  • 優點:可以做到在不修改目標物件的功能的前提下,對目標功能擴充套件
  • 缺點:因為代理物件需要與目標物件實現一樣的介面,所以會有很多代理類,類太多。同時,一旦介面增加方法,目標物件與代理物件都要維護。

如何解決靜態代理中的缺點呢?答案是動態代理。

動態代理

剛剛我們講的都是靜態代理,所謂的靜態代理,就是自己要為要代理的類寫一個代理類,或者用工具為其生成的代理類,總之,就是程式執行前就已經存在的編譯好的代理類,這樣有時會覺得非常麻煩,也導致非常不靈活,相比靜態代理,動態帶來具有更強的靈活性,因為它不用我們在設計實現的時候就指定某一個代理類來代理哪一個被代理物件,我們可以把這種指定延時到程式執行時由JVM 來實現。

還是剛剛那個買車的例子

public class Client {
    public static void main(String[]args){
    IBuyCar customer = (IBuyCar)Proxy.newProxyInstance(IBuyCar.class.getClassLoader(), IBuyCar.class.getInterfaces(), new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //在轉調具體目標物件之前,可以執行一些功能處理

            //轉調具體目標物件的方法
            Object invoke = method.invoke(this, args);

            //在轉調具體目標物件之後,可以執行一些功能處理
            return invoke;
        }
    });
    customer.buyCar();
    }
}複製程式碼

以上,就是動態代理的寫法
可能有同學會這樣寫好像不太符合我們的需求,我怎麼根據客戶的現金數去判斷能否買車呢?
我是這樣理解的,因為動態代理是為了解決靈活性,所以一般不推薦強繫結業務物件,這裡我們一般用於解決某些具有共性的場景,我們可以在代理方法呼叫前後分別幹一些想幹的事情。如果一定要強繫結業務,那麼推薦使用靜態代理哦。
當然,可能會有同學要鑽牛角尖,我就要用動態代理實現買車的需求,且控制金額。這裡我還是寫一下程式碼實現吧,我不推薦這種做法哦,具體看業務邏輯吧~

直接貼程式碼吧,建立了DynamicProxy 類繼承了InvocationHandler,然後在invoke 方法裡面判斷就好。

優缺點

  • 優點
    • 減少程式設計的工作量
    • 系統擴充套件性和可維護性增強
  • 缺點
    • 使用了反射,降低效能

擴充套件

再來兩個小的擴充套件知識點吧~

CGLIB 代理

上面的靜態代理和動態代理模式都是要求目標實現一個介面的目標物件,但是有時候目標物件只是一個單獨的物件,並沒有實現任何接,這個時候就可以使用以目標物件子類的方式實現代理,這種方法叫做 cglib 代理。

cglib 代理又稱子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件。

CGLIB(Code Generation Library)是一個開源專案!

  • JDK 的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用CGLIB 實現。
  • CGLIB 是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件 Java 類與實現 java 介面。它廣泛的被許多 AOP的框架使用,例如 Spring。
  • CGLIB 包的底層是通過使用一個小而快的位元組碼處理框架 ASM 來轉換位元組碼生成新的類。不鼓勵直接使用 ASM,因為它要求你必須對 JVM 內部結構包括 class 檔案的格式和指令集都很熟悉。

好了,沒看懂 CGLIB 的實現原理沒關係。我們只是簡單瞭解一下,下面還是以買車的例子來講解。

首先,執行時通過 ASM 來生成一個代理類 CGLIBProxy,繼承子 Customer,然後重寫對應的方法,看程式碼。

public class CGLIBProxy extends Customer{

    @Override
    public void buyCar(){
    //在轉調具體目標物件之前,可以執行一些功能處理

    //轉調具體目標物件的方法
    super.buyCar();  

    //在轉調具體目標物件之後,可以執行一些功能處理

    }

}複製程式碼

以上,大概就是醬紫吧。在 Android 開發中好像沒用到這玩意,我們知道這回事就行了哈。

Retrofit 中的代理模式

之所以單獨寫代理模式,主要是因為他喵的二十三種設計模式,就代理沒看懂,以至於我每次在研究 Retrofit 原始碼的時候,看到代理全程懵逼。

不多說了,直接看 Retrofit.create()方法

public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
  eagerlyValidateMethods(service);
}
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);
        }
        //載入處理API介面方法
        ServiceMethod serviceMethod = loadServiceMethod(method);
        //建立OkHttpCall
        OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
        //通過對應的CallAdapter適配自定義並期望返回的Call
        return serviceMethod.callAdapter.adapt(okHttpCall);
      }
    });複製程式碼

}

這裡我們在呼叫Service 方法的時候,根據方法引數,建立了ServiceMethod。至於ServiceMethod是幹嘛的,這裡不作詳細講解了。

/** Adapts an invocation of an interface method into an HTTP call. */
final class ServiceMethod<T> 複製程式碼

翻譯:將介面方法的呼叫改編為HTTP呼叫

相關文章