Retrofit源碼分析二 代理模式

Retrofit源碼分析二 代理模式

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

靜態代理

靜態代理類圖
從上面的類圖中咱們能夠了解到,RealClass和ProxyClass都繼承了AbstractClass,都實現AbstractClass中的operation方法。其中ProxyClass包含了一個RealClass的引用,在調用ProxyClass中的operation方法時,調用了RealClass類型的引用對象的operation方法,這就是靜態代理。只是這麼說有些抽象,下面咱們來看一個具體的代碼實現。react

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方法以前加入權限校驗,從而實現了上述的功能。設計模式

動態代理

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

  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經過動態代理的方式爲咱們生成了一個代理對象,爲咱們的接口方法擴展了網絡訪問的功能。理解這一點,對咱們後續的源碼分析很是重要。ide

相關文章
相關標籤/搜索