【一起學習Android開源框架】Retrofit使用的代理模式解析(第二部分)

語言: CN / TW / HK

highlight: a11y-light theme: vue-pro


「這是我參與11月更文挑戰的第1天,活動詳情檢視:2021最後一次更文挑戰

這部分同學我簡單學習下Retrofit的代理模式

  1. 關於代理模式,簡單來說為其他物件提供一種代理,用以控制對這個物件的訪問
  2. 從字面意思可以這麼理解為,所謂代理代理,其實就是代替別人去做我們想做的事,比方說,我們需要海外購物的時候,由於種種原因在國內,這時候就需要找些朋友或者代購,這個代理是如何去購買東西的過程我們是不用關心的,只需要在意收到的東西這個結果就可以了.

代理模式又分為兩種,靜態代理或者動態代理,下面我們來詳細看看

靜態代理

靜態代理介紹

首先看下下面這張類圖,一個靜態代理需要三個角色,分別是抽象物件(AbstractObject), 代理物件(ProxyObject)以及目標物件(RealObject)

retrofit代理.png

  • 對於抽象物件角色,聲明瞭目標類和代理物件的共同介面,定義了一個方法是代理物件和目標物件所共同繼承的, 這樣就可以在任何地方,可以使用目標物件,都是用ProxyObject來定義,這就體現了代理的意義
  • 對於目標物件角色,這裡定義了代理物件所代表的目標物件
  • 對於代理物件角色,串聯了整個靜態代理模式的關鍵角色,在這個代理物件內部持有一個realObject的引用,可以在任何時候操作realObject,由於proxyObject和realObject有統一的介面,方便任何時候替換掉目標物件

靜態代理實現

下面我們來簡單看下靜態代理模式的程式碼實現

  1. 首先我們要定義一個AdstractObject,代表的抽象物件角色 ```kotlin abstract class AbstractObject {

    abstract fun operation() } ```

  2. 定義目標實現類RealObject kotlin class RealObject : AbstractObject() { override fun operation() { println("do operation....") } } 這裡繼承抽象類,實現了抽象方法

  3. 第三步就是定義我們的代理類ProxyObject,一樣繼承了抽象類,並且實現了抽象方法 ```kotlin class ProxyObject(private var realObject: RealObject) : AbstractObject() {

    override fun operation() { println("do something before real operation...") realObject = RealObject() realObject.operation() println("do something after real") }

} ``` 這裡和目標實現類不同的是,這個抽象方法可以做一些其他邏輯,這就是靜態代理的作用之一

從這個例子可以看出代理物件將我們客戶端的呼叫委派給了目標物件,而在呼叫這個目標物件之前/之後都可以新增自己的判斷

動態代理

動態代理模式介紹

下面我們可以來看下Retrofit中用到的動態代理模式,它的顯著特點如下: - 無侵入式的可以擴充套件程式碼 - 通俗點來說,不修改原來程式碼的情況下,增強一些方法或者功能,在方法執行前後可以做任何想做的事情

那麼什麼是動態代理呢?

簡單來說,就是在程式執行的時候建立的代理方式,相對於靜態代理有一個很大的優勢,很方便的對我們代理類函式進行統一處理,不用每一個函式進行單獨處理

兩種動態代理寫法

  • jdk動態代理

  • 這個寫法需要自己的客戶端寫輔助介面來進行操作的

  • 是由java內部的反射機制來實現的,這個在生成類的時候會比較高效

  • CGLIB

  • 直接修改位元組碼來做的

動態代理實現

我們通常都是使用jdk動態代理這個寫法,下面通過一個例子來簡單說明下(需要值得謹記的是jdk動態代理只能為介面建立代理)

  1. 第一步建立一個Subject的介面,定義一個shopping方法 kotlin interface Subject { fun shopping() }

  2. 第二步實現一個被代理類,實現Subject介面方法 kotlin class Man : Subject { override fun shopping() { println("Jacky購物去了") } }

  3. 第三步要實現Proxy代理類,實現一個InvocationHandler介面 ```kotlin class Proxy(private var target: Any) : InvocationHandler {

    override fun invoke(proxy: Any?, method: Method?, args: Array?): Any? { println("proxy:${proxy?.javaClass?.name}") println("before...") method?.invoke(target,args) println("after...") return null } } ``` - 每個動態代理都需要實現這個InvocationHadnler介面的invoke方法 - invoke方法三個引數分別表示為:proxy指代代理的真實物件,method就是所呼叫真實物件的某個方法,args表示所呼叫真實物件某個方法的某個引數

關於InvocationHandler

為什麼要使用這三個引數呢?我們暫時先保留疑問,之後再深入探索下,這裡簡單總結下InvocationHandler,經過查驗學習,我發現其中設計到攔截的概念

  • 每個代理類的物件都會關聯一個表示內部處理邏輯的InvocationHandler介面的實現,當使用者呼叫了代理物件所代理介面的方法,這個呼叫資訊就會被傳遞到invoke方法中

  • invoke方法的引數中可以獲取引數(代理物件,方法對應的method物件以及方法中的引數)

  • invoke方法的返回值被返回給使用者,表明對方法呼叫進行了攔截了,所有的過程對使用者來說是透明的,在Retrofit當中會經常出現的

  • 第四步我們進行客戶端Client類的實現 ```kotlin object Client { @JvmStatic fun main(args: Array) {

    val man = Man()
    val p = Proxy(man)
    
    //通過java.lang.reflect.newProxyInstance()方法獲取真實物件的代理
    val subject:Subject = java.lang.reflect.Proxy.newProxyInstance(
        man.javaClass.classLoader,man.javaClass.interfaces,p) as Subject
    //通過代理物件呼叫真實物件相關介面實現方法
    subject.shopping()
    //獲取真實物件的代理物件對應的Class物件的名稱,用字串表示
    println(subject.javaClass.name)
    

    } } - 這裡呼叫**Proxy.newProxyInstance**方法,就是用於建立我們所需要的代理物件,可以看下它的原始碼java public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); ······ ``` 簡單介紹下三個引數,分別是ClassLoader,Class<?>[],InvocationHandler - ClassLoader:類載入器,把我們動態生成的代理類的位元組碼檔案,載入到JVM虛擬機器中 - Class<?>[]:class類陣列的介面,為代理物件提供介面 - 最重要的引數InvocationHandler,就是關聯到該物件呼叫其invoke方法

回到Client類,梳理下動態代理的過程

  1. 首先呼叫Proxy.newProxyInstance方法,獲取這個真實物件的代理物件
  2. 每當這個代理類物件執行某個方法的時候,就會呼叫這個代理物件所關聯handler中的invoke方法執行相應的操作 其實這就是完整的jdk動態代理的過程

動態代理總結

  1. 執行期(動態代理通過代理類與相關介面不直接發生聯絡的情況下,而在執行時實現動態關聯)
  2. InvocationHandler介面和反射包中的Proxy類
  3. 動態代理與靜態代理最大的不同在於動態代理的代理類不需要手動生成,是在程式碼執行期動態生成的

總結

  • 以上就是關於Retrofit中所經常使用的代理模式的簡單分析(動態代理和靜態代理),簡單預熱下,Retrofit程式碼量不是特別多,但是它所設計的設計模式非常非常多,所以需要深入瞭解下
  • 接下來關於Retrofit的原始碼分析學習,分析它的整體流程以及學習它核心的原始碼部分,鋪墊研究了許久,歡迎大家和我一起學習