Retrofit 原始碼解析 - 動態代理

wzgiceman發表於2017-01-19

背景

之前一系列的關於Retrofit使用和封裝的講解過後,想必對Retrofit的靈活性和擴充套件性有何深入的瞭解,既然如此我們就對於Retrofit內部實現原理來深入的學習,既然要用就要理解怎麼用和怎麼能用的的更好,不能侷限在使用的層面上,接下來的文章從原始碼的角度去思考和借鑑如何才能寫出一個好的開源框架。

RxRetrofit封裝-專欄

原理

Retrofit 2.0是如何進行網路請求的呢?主要是用到了Java的動態代理,所以在深入學習之前,先來了解下Java的動態代理

何為Java動態代理

其實Java動態代理就是設計模式中的代理模式,特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。代理類與委託類之間通常會存在關聯關係,一個代理類的物件與一個委託類的物件關聯,代理類的物件本身並不真正實現服務,而是通過呼叫委託類的物件的相關方法,來提供特定的服務。

通俗來說:比如登入之前需要判斷使用者資訊是否完整、請求之前需要配置請求資料等等,動態代理就是來處理這樣的需求,讓使用者只需要關心事件處理

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

  • 靜態代理:由程式設計師建立或特定工具自動生成原始碼,再對其編譯。在程式執行前,代理類的.class檔案就已經存在了。

  • 動態代理:在程式執行時,運用反射機制動態建立而成。

靜態代理

靜態代理其實就是按照代理模式,在實現了代理類和委託類之後的呼叫方式,可以把它看成是代理模式的寫法

介面類

/**
 * 介面
 * Created by WZG on 2017/1/19.
 */

public interface HttpRequest {

    /**
     * 請求
     */
    void request();
}複製程式碼

委託類

/**
 * 委託類
 * Created by WZG on 2017/1/19.
 */

public class HttpRequestImpl implements HttpRequest {
    @Override
    public void request() {
        Log.e("tag","-------->htt請求");
    }
}複製程式碼

代理類

/**
 * 代理類-靜態
 * Created by WZG on 2017/1/19.
 */

public class HttpRequestProxy implements HttpRequest {

    HttpRequestImpl httpRequest;


    public HttpRequestProxy(HttpRequestImpl httpRequest) {
        this.httpRequest = httpRequest;
    }

    @Override
    public void request() {
        Log.e("tag","靜態--------->http請求前");
        httpRequest.request();
        Log.e("tag","靜態--------->http請求後");
    }

}複製程式碼

呼叫

 /*靜態代理*/
 HttpRequestImpl request = new HttpRequestImpl();
 HttpRequestProxy proxy = new HttpRequestProxy(request);
        proxy.request();複製程式碼

結果

Retrofit 原始碼解析 - 動態代理
這裡寫圖片描述

動態代理

動態代理,字面可理解在代理類中委託類是動態生成的,及時一個動態代理類可處理多個委託類,從而簡化代理類的處理。

Java動態代理分為兩種

  • JDK動態代理-自帶

  • CGlib動態代理-第三方

JDK動態代理

JDK動態代理載入器 Proxy

public class Proxy implements Serializable {
    protected InvocationHandler h;

    protected Proxy(InvocationHandler h) {
        throw new RuntimeException("Stub!");
    }

    public static Class<?> getProxyClass(ClassLoader loader, Class... interfaces) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }

    public static boolean isProxyClass(Class<?> cl) {
        throw new RuntimeException("Stub!");
    }

    public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
}複製程式碼

在Proxy類中的newProxyInstance()方法中需要一個ClassLoader類的例項,ClassLoader實際上對應的是類載入器,在Java中主要有一下三種類載入器;

  • Booststrap ClassLoader:此載入器採用C++編寫,一般開發中是看不到的;

  • Extendsion ClassLoader:用來進行擴充套件類的載入,一般對應的是jre\lib\ext目錄中的類;

  • AppClassLoader:(預設)載入classpath指定的類,是最常使用的是一種載入器。

這裡使用第三種AppClassLoader,主要使用newProxyInstance方法去載入

引數 含義
ClassLoader loader 指被代理的物件
Class<?>[] interfaces 要呼叫的方法
InvocationHandler h 方法呼叫時所需要的引數

JDK動態代理類處理方法主要是依賴InvocationHandler介面


public interface InvocationHandler {
    Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
}複製程式碼

invoke方法是動態代理類處理的主要方法

引數 含義
Object var1 指被代理的物件
Method var2 要呼叫的方法
Object[] var3 方法呼叫時所需要的引數

代理類實現

共用HttpRequest介面,不在重複描述


/**
 * 動態代理-jdk
 * Created by WZG on 2017/1/19.
 */

public class HttpRequestJDKProxy implements InvocationHandler {

    private Object target;
    /**
     * 繫結委託物件並返回一個代理類
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        //取得代理物件
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);   //要繫結介面(這是一個缺陷,cglib彌補了這一缺陷)
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        Log.e("tag","jdk--------->http請求前");
        Object  result=method.invoke(target, objects);
        Log.e("tag","jdk--------->http請求後");
        return result;
    }
}複製程式碼

呼叫

       /*jdk動態代理*/
        HttpRequestJDKProxy jdkProxy = new HttpRequestJDKProxy();
        HttpRequest httpRequest = (HttpRequest) jdkProxy.bind(request);
        httpRequest.request();複製程式碼

結果

Retrofit 原始碼解析 - 動態代理
這裡寫圖片描述

CGlib動態代理

CGlib-原始碼

CGlib動態代理其實是國外一個開源作者寫的一個開源庫,幫助完善jdk動態代理中

要繫結介面(這是一個缺陷)
Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);複製程式碼

JDK的動態代理機制只能代理實現了介面的類,而不能實現介面的類就不能實現JDK的動態代理,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為採用的是繼承,所以不能對final修飾的類進行代理

CGlib代理類


/**
 * cglib動態代理類
 * Created by WZG on 2017/1/19.
 */

public class HttpRequestCglibProxy implements MethodInterceptor {

    /**
     * 建立代理物件
     *
     * @param target
     * @return
     */
    public Object getInstance(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        // 回撥方法
        enhancer.setCallback(this);
        // 建立代理物件
        return enhancer.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Log.e("tag", "Cglib--------->http請求前");
        Object result= methodProxy.invokeSuper(o, objects);
        Log.e("tag", "Cglib--------->http請求後");
        return result;

    }
}複製程式碼

呼叫

       /*cglib動態代理*/
        HttpRequestCglibProxy httpRequestCglibProxy=new HttpRequestCglibProxy();
        HttpRequest httpRequestCglib = (HttpRequest) httpRequestCglibProxy.getInstance(new HttpRequestCglibImpl());
        httpRequestCglib.request();複製程式碼

但是由於Android中和java環境的不一,導致在Android專案中其實並不能使用CGlib這種動態代理方式,在java環境中是可以使用這個動態庫,所以這裡沒有顯示最後的輸出結果,eclipsejava專案是完全可以的(驗證過)

動態代理其實主流的使用是在Spring中,最近一段時間才剛剛流行到Android,其實除了CGlib動態代理以外,還有另外一種好用的動態生成思路AOP面向切片

AOP面向切片

想必對OOP再熟悉不過了,那AOP是什麼呢?AOP就是把涉及到眾多模組的某一類問題進行統一管理,橫向的思考需求,然後統一管理處理通用的或者是公用的邏輯,降低專案耦合度,提升效率,簡化多餘程式碼,一般AOP結合Aspect(也是Spring技術中大量使用),但是在Android中也有對應的變種AspectJ.

當然由於本文是Retrofit原始碼解析-動態代理這裡就不過多的闡述關於AOP的技術了,有興趣的同學可以參考JakeWharton-hugo

JakeWharton-hugo

總結

Retrofit原始碼解析-動態代理其中使用的就是本文中的第二種動態代理JDK動態代理,所以使得Retrofit在擴充套件性和維護行方面得到很大的提升,但是其實也是有缺點的,因為JDK動態代理裡面過多的使用了反射的機制,所以在效率方面是不及CGlib代理處理的,估計也是由於不支援Android專案,所以被迫選擇這種方法,個人理解

專欄

RxJava+Retrofit+OkHttp深入淺出-終極封裝專欄)

原始碼

下載原始碼

建議

如果你有任何的問題和建議歡迎加入QQ群告訴我!

相關文章