上一節咱們講了一些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
咱們在這裏主要講解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