Retrofit原始碼分析二 代理模式

BlackFlagBin發表於2018-04-17

Retrofit原始碼分析二 代理模式

上一節我們講了一些Retrofit的概覽,這一節我們主要來說一下代理模式。有同學可能要問,這不是Retrofit的原始碼分析嗎,怎麼都第二節了還不分析原始碼呢?其實Retrofit這個框架中應用了很多的設計模式,其中最重要的就是動態代理模式。如果我們要理解並掌握Retrofit,那麼就必須先掌握代理模式。代理模式主要分為兩種,靜態代理和動態代理,下面我們來細細的說明一下。

靜態代理

靜態代理類圖
從上面的類圖中我們可以瞭解到,RealClass和ProxyClass都繼承了AbstractClass,都實現AbstractClass中的operation方法。其中ProxyClass包含了一個RealClass的引用,在呼叫ProxyClass中的operation方法時,呼叫了RealClass型別的引用物件的operation方法,這就是靜態代理。只是這麼說有些抽象,下面我們來看一個具體的程式碼實現。

package com.blackflagbin.frameanalysis.staticproxy;

//抽象日誌類
public abstract class AbstractLogger {
    abstract public void log();
}
複製程式碼
package com.blackflagbin.frameanalysis.staticproxy;

//真實操作日誌類,繼承AbstractLogger
public class RealLogger extends AbstractLogger {
    @Override
    public void log() {
        System.out.println("show some log");
    }
}
複製程式碼
package com.blackflagbin.frameanalysis.staticproxy;

//代理日誌類,包含一個真實日誌類的例項,在列印日誌之前校驗許可權
public class ProxyLogger extends AbstractLogger {

    private AbstractLogger mLogger;

    public ProxyLogger(AbstractLogger logger) {
        mLogger = logger;
    }

    private boolean checkPermission() {
        return true;
    }

    @Override
    public void log() {
        if (checkPermission()) {
            mLogger.log();
        } else {
            System.out.println("you have no access");
        }
    }
}
複製程式碼

上面三個類分別是抽象日誌類、真實日誌類、代理日誌類。抽象日誌類定義了一個列印日誌的介面,真實日誌類繼承了抽象日誌,並實現的這個列印日誌的方法。這個時候,我們想要在列印日誌前加上許可權校驗,又不想直接修改我們的真實日誌類,那麼就需要使用的靜態代理模式。為了實現在列印日誌前校驗許可權的功能,我們建立了一個新類,代理日誌類,這個類同樣繼承了抽象日誌類,關鍵的是包含了一個真實日誌類的引用物件。在這個代理類中的log方法中,通過在呼叫真實日誌引用物件的log方法之前加入許可權校驗,從而實現了上述的功能。

動態代理

動態代理與靜態代理最大的區別在於動態。那麼問題來了,這個動態體現在哪裡,又該如何去理解? 這個動態關鍵在於代理類的建立時機。靜態代理中的代理類在我們執行程式之前是必須要存在的,而動態代理中的代理類則是在程式執行時建立的。前半句很好理解,代理類的程式碼肯定是先存在,然後才能執行,這個邏輯很符合我們平常的開發模式。問題就在於後半句,代理類在執行時建立,執行時如何建立代理類?這是不是很反邏輯?程式碼都沒有,怎麼來根據我們的需求來代理真實的被代理的物件?諸位稍安勿躁,我們接下來會對動態代理進行詳細的解釋。 動態代理有兩種實現方式:

  1. JDK動態代理
  2. CGLIB 我們在這裡主要講解JDK動態代理。JDK動態代理底層是通過Java的反射實現的,而且只能為介面建立動態代理,而靜態代理則沒有這種限制(介面或者抽象類都可以)。下面我們通過一些程式碼來看一下動態代理的實現方式。同樣,我們使用列印日誌的這個例子,方便大家理解。
package com.blackflagbin.frameanalysis.dynamicproxy;

//日誌介面(動態代理不同於靜態代理,只能使用介面)
public interface ILogger {
    void log();
}
複製程式碼
package com.blackflagbin.frameanalysis.dynamicproxy;

//真實日誌類,實現日誌介面
public class RealLogger implements ILogger {
    @Override
    public void log() {
        System.out.println("show some log");
    }
}
複製程式碼
package com.blackflagbin.frameanalysis.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyHandler implements InvocationHandler {

    //被代理物件(即目標物件)的例項,在列印日誌這個例子中對應RealLogger的例項
    private final Object mTarget;

    public ProxyHandler(Object target) {
        mTarget = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (checkPermission()) {
            System.out.println("you have access");
            //被代理物件(即目標物件)方法的呼叫
            return method.invoke(mTarget, args);
        } else {
            System.out.println("you have no access");
            return null;
        }
    }

    //建立實際的代理物件
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(mTarget.getClass().getClassLoader(), mTarget.getClass().getInterfaces(), this);
    }

    private boolean checkPermission() {
        return true;
    }

}
複製程式碼
package com.blackflagbin.frameanalysis.dynamicproxy;

//測試類
public class Test {
    public static void main(String[] args) {
        ProxyHandler proxyHandler = new ProxyHandler(new RealLogger());
        ILogger proxy = (ILogger) proxyHandler.getProxyInstance();
        proxy.log();
    }
}
/*
最終列印結果:
you have access
show some log
 */

複製程式碼

日誌介面和真實日誌類沒什麼可說的,跟靜態代理一樣,只不過因為動態代理必須要使用介面所以把抽象類換成了介面。 動態代理實現列印日誌這個例子的關鍵在於ProxyHandler這個類。我們知道,在靜態代理中,對原有功能進行擴充套件或修改的程式碼實現是在靜態代理類中定義的。也就是在ProxyLogger中新增額外的許可權校驗方法,並修改列印日誌的流程。那麼問題來了,在動態代理中,動態代理類是在程式執行時生成的,我們並沒有事先宣告一個動態代理類,這個對原有功能進行擴充套件或修改的程式碼實現究竟要放在哪裡? 問題的答案在這個列印日誌的例子裡已經很明確了,對原有功能進行擴充套件或修改的程式碼實現放在了一個類中,這個類實現了InvocationHandler這個介面。我們通過對invoke這個方法的修改來修改被代理物件的方法實現,通過在invoke方法中的method.invoke(mTarget, args)之前或之後插入我們想要的邏輯來增對原有功能進行擴充套件。 在這個ProxyHandler類中,我們還新增了一個getProxyInstance()方法來建立代理類物件。通過Proxy.newProxyInstance(mTarget.getClass().getClassLoader(), mTarget.getClass().getInterfaces(), this)來建立代理類的例項是固定的寫法,newProxyInstance需要傳入三個引數,分別是類載入器、介面陣列和實現InvocationHandler的類的例項物件。 我們再來總結一下。無論是靜態代理還是動態代理,它們的本質都是代理物件包含一個被代理物件的例項,從而對被代理物件的原有功能進行擴充套件或修改。最大的區別是代理類的建立時機不同,靜態代理必須在程式執行前寫好代理類;而動態代理的代理類則不需要我們手動提前寫好,它會在執行時建立相應的代理類。 值得再次強調的是,雖然動態代理不需要我們在程式碼中實現代理類,但是對原有功能進行擴充套件或修改的程式碼實現是必須提前寫好的。這個很好理解,如果開發人員都不寫清楚要如何對原有功能進行擴充套件或修改,計算機又怎麼知道呢?所以對原有功能進行擴充套件或修改的程式碼實現就必須提前寫好,問題是這些程式碼要放在那裡,為了解決這個問題,Java提供了一個InvocationHandler的介面,我們只要把相應的程式碼放到這個介面的實現類中即可。生成的代理物件在呼叫相應的方法時,實際上呼叫的是invoke這個方法,從而實現了對被代理物件的原有功能進行擴充套件或修改。 最後,我再貼上一些程式碼。

package com.zhidian.cloudforpolice.common.http

import com.zhidian.cloudforpolice.BuildConfig
import com.zhidian.cloudforpolice.common.entity.http.*
import com.zhidian.cloudforpolice.common.entity.http.Unit
import io.reactivex.Observable
import retrofit2.http.*

/**
 * Created by blackflagbin on 2018/1/27.
 */
interface ApiService {

    //登入
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}account/login.do")
    fun login(@Field("username") userName: String, @Field("password") pwd: String, @Field("clientType") clientType: Int): Observable<HttpResultEntity<UserEntity>>

    //登出
    @POST("${BuildConfig.EXTRA_URL}account/logout.do")
    fun logout(): Observable<HttpResultEntity<Any>>

    //獲取小區列表
    @GET("${BuildConfig.EXTRA_URL}community/name/list")
    fun getCommunityList(): Observable<HttpResultEntity<List<CommunityEntity>>>

    //獲取首頁資訊
    @GET("${BuildConfig.EXTRA_URL}count/communityStatistic/{communityId}.do")
    fun getMainData(@Path("communityId") communityId: Int): Observable<HttpResultEntity<MainEntity>>

    //根據小區id獲取小區樓幢列表
    @GET("${BuildConfig.EXTRA_URL}community/block/name/list/{communityId}")
    fun getBuildingList(@Path("communityId") communityId: Int): Observable<HttpResultEntity<List<BuildingEntity>>>

    //根據樓幢id獲取樓棟下的房間列表
    @GET("${BuildConfig.EXTRA_URL}community/block/detail/{blockId}")
    fun getRoomList(@Path("blockId") blockId: Int): Observable<HttpResultEntity<BuildingInfoEntity>>

    //根據單元id獲取單元下樓層列表
    @GET("${BuildConfig.EXTRA_URL}community/unit/query/{unitId}")
    fun getFloorList(@Path("unitId") unitId: Int): Observable<HttpResultEntity<Unit>>

    //根據房間id獲取房間詳情
    @GET("${BuildConfig.EXTRA_URL}community/room/detail/{roomId}")
    fun getRoomInfo(@Path("roomId") roomId: Int): Observable<HttpResultEntity<RoomInfoEntity>>

    //根據條件查詢,獲取符合條件的居民列表
    @GET("${BuildConfig.EXTRA_URL}community/resident/list/{pageNo}/{limit}")
    fun getSearchedPersonList(
            @Path("pageNo") pageNo: Int, @Path(
                    "limit") limit: Int, @QueryMap map: Map<String, String>): Observable<HttpResultEntity<PersonEntity>>

    //根據使用者id獲取關聯房屋列表
    @GET("${BuildConfig.EXTRA_URL}community/resident/room/list/{userId}")
    fun getRelatedRoomList(@Path("userId") userId: Int): Observable<HttpResultEntity<List<RelatedRoomEntity>>>

    //獲取樓幢詳情
    @GET("${BuildConfig.EXTRA_URL}count/blockStatistic/{buildingId}.do")
    fun getBuildingDetail(@Path("buildingId") buildingId: Int): Observable<HttpResultEntity<BuildingDetailEntity>>

    //獲取一級標籤
    @GET("${BuildConfig.EXTRA_URL}user/label/parent/list")
    fun getFirstLabel(): Observable<HttpResultEntity<List<LabelItemEntity>>>

    //獲取二級標籤
    @GET("${BuildConfig.EXTRA_URL}user/label/child/list/{parentId}")
    fun getSecondLabel(@Path("parentId") parentId: Int): Observable<HttpResultEntity<List<LabelItemEntity>>>

    //修改密碼
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}account/password/update")
    fun changePwd(@Field("password") oldPwd: String, @Field("newPsw") newPwd: String): Observable<HttpResultEntity<Any>>


    //門禁線上
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}device/query")
    fun getDeviceList(@Field("communityId") communityId: Int, @Field("pageNo") pageNo: Int, @Field("limit") limit: Int): Observable<HttpResultEntity<DoorEntity>>


    //獲取門禁狀態
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}device/selectDeviceStatusCount")
    fun getDeviceStatusList(@Field("communityId") communityId: Int): Observable<HttpResultEntity<List<DeviceStatusItem>>>


    //根據條件查詢,獲取警情處理規範列表
    @GET("${BuildConfig.EXTRA_URL}police/handle/norm/list/{pageNo}/{limit}")
    fun getPoliceHandleList(
            @Path("pageNo") pageNo: Int, @Path(
                    "limit") limit: Int, @QueryMap map: Map<String, String>): Observable<HttpResultEntity<PoliceHandleEntity>>

    //根據條件查詢,獲取重點人員預警列表
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}police/person/query")
    fun getPersonPreWarningList(
            @FieldMap map: Map<String, String>): Observable<HttpResultEntity<PersonPreWarningEntity>>


    //根據條件查詢,獲取觸警人員預警列表
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}police/person/queryContactPolice")
    fun getAttackPreWarningList(
            @FieldMap map: Map<String, String>): Observable<HttpResultEntity<PersonPreWarningEntity>>


    //根據條件查詢,獲取重點房屋預警列表
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}police/room/query")
    fun getRoomPreWarningList(
            @FieldMap map: Map<String, String>): Observable<HttpResultEntity<RoomPreWarningEntity>>

    //獲取行為軌跡
    @GET("${BuildConfig.EXTRA_URL}user/info/live/history/{userId}")
    fun getMovePath(
            @Path("userId") userId: String): Observable<HttpResultEntity<List<MovePathItemEntity>>>

    //開門
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}device/operate/1")
    fun openLock(
            @Field("devicdId") deviceId: String): Observable<HttpResultEntity<Any>>

    //更新人員預警狀態
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}police/person/updStatu")
    fun updatePersonPreWaning(
            @Field("id") id: String, @Field("statu") status: Int, @Field("processContent") processContent: String): Observable<HttpResultEntity<Any>>

    //更新房屋預警狀態
    @FormUrlEncoded
    @POST("${BuildConfig.EXTRA_URL}police/room/updStatu")
    fun updateRoomPreWaning(
            @Field("id") id: String, @Field("statu") status: Int, @Field("processContent") processContent: String): Observable<HttpResultEntity<Any>>


    //獲取二維碼返回結果
    @FormUrlEncoded
    @POST("api/qrcode/parsingcode")
    fun getQrCodeResult(
            @Field("code") qrcode: String): Observable<HttpResultEntity<String>>


}
複製程式碼

這是個Kotlin寫的介面類,用過Retrofit的同學應該很清楚,使用Retrofit進行網路請求,首先就是要建立一個網路請求介面類。

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://......../")
    .build();

ApiService service = retrofit.create(ApiService.class);
複製程式碼

看到**ApiService service = retrofit.create(ApiService.class)**有沒有很熟悉的感覺?聰明的小夥伴看到這裡應該就會明白了,沒錯,這其實就是通過動態代理建立了一個代理物件。我們只是寫了一個網路介面類,裡面什麼都沒實現,為什麼就可以正確的請求網路幷包裝返回的資料結果?如果你從上到下把這篇文章看完了,即使你現在還並不清楚裡面具體的程式碼細節,但有一點你會非常明確:**我們寫的ApiService這個介面類並不具有訪問網路幷包裝返回資料結果的功能,是Retrofit通過動態代理的方式為我們生成了一個代理物件,為我們的介面方法擴充套件了網路訪問的功能。**理解這一點,對我們後續的原始碼分析非常重要。

相關文章