Retrofit源碼分析二 代理模式
上一節(jié)我們講了一些Retrofit的概覽面哼,這一節(jié)我們主要來(lái)說(shuō)一下代理模式橡疼。有同學(xué)可能要問(wèn)素邪,這不是Retrofit的源碼分析嗎,怎么都第二節(jié)了還不分析源碼呢卸伞?其實(shí)Retrofit這個(gè)框架中應(yīng)用了很多的設(shè)計(jì)模式抹镊,其中最重要的就是動(dòng)態(tài)代理模式。如果我們要理解并掌握Retrofit荤傲,那么就必須先掌握代理模式垮耳。代理模式主要分為兩種,靜態(tài)代理和動(dòng)態(tài)代理遂黍,下面我們來(lái)細(xì)細(xì)的說(shuō)明一下终佛。
靜態(tài)代理
從上面的類圖中我們可以了解到,RealClass和ProxyClass都繼承了AbstractClass雾家,都實(shí)現(xiàn)AbstractClass中的operation方法铃彰。其中ProxyClass包含了一個(gè)RealClass的引用,在調(diào)用ProxyClass中的operation方法時(shí)芯咧,調(diào)用了RealClass類型的引用對(duì)象的operation方法牙捉,這就是靜態(tài)代理。只是這么說(shuō)有些抽象敬飒,下面我們來(lái)看一個(gè)具體的代碼實(shí)現(xiàn)邪铲。
package com.blackflagbin.frameanalysis.staticproxy;
//抽象日志類
public abstract class AbstractLogger {
abstract public void log();
}
package com.blackflagbin.frameanalysis.staticproxy;
//真實(shí)操作日志類,繼承AbstractLogger
public class RealLogger extends AbstractLogger {
@Override
public void log() {
System.out.println("show some log");
}
}
package com.blackflagbin.frameanalysis.staticproxy;
//代理日志類无拗,包含一個(gè)真實(shí)日志類的實(shí)例带到,在打印日志之前校驗(yàn)權(quán)限
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");
}
}
}
上面三個(gè)類分別是抽象日志類、真實(shí)日志類英染、代理日志類揽惹。抽象日志類定義了一個(gè)打印日志的接口被饿,真實(shí)日志類繼承了抽象日志,并實(shí)現(xiàn)的這個(gè)打印日志的方法搪搏。這個(gè)時(shí)候狭握,我們想要在打印日志前加上權(quán)限校驗(yàn),又不想直接修改我們的真實(shí)日志類慕嚷,那么就需要使用的靜態(tài)代理模式哥牍。為了實(shí)現(xiàn)在打印日志前校驗(yàn)權(quán)限的功能,我們創(chuàng)建了一個(gè)新類喝检,代理日志類,這個(gè)類同樣繼承了抽象日志類撼泛,關(guān)鍵的是包含了一個(gè)真實(shí)日志類的引用對(duì)象挠说。在這個(gè)代理類中的log方法中,通過(guò)在調(diào)用真實(shí)日志引用對(duì)象的log方法之前加入權(quán)限校驗(yàn)愿题,從而實(shí)現(xiàn)了上述的功能损俭。
動(dòng)態(tài)代理
動(dòng)態(tài)代理與靜態(tài)代理最大的區(qū)別在于動(dòng)態(tài)。那么問(wèn)題來(lái)了潘酗,這個(gè)動(dòng)態(tài)體現(xiàn)在哪里杆兵,又該如何去理解?
這個(gè)動(dòng)態(tài)關(guān)鍵在于代理類的創(chuàng)建時(shí)機(jī)仔夺。靜態(tài)代理中的代理類在我們運(yùn)行程序之前是必須要存在的琐脏,而動(dòng)態(tài)代理中的代理類則是在程序運(yùn)行時(shí)創(chuàng)建的。前半句很好理解缸兔,代理類的代碼肯定是先存在日裙,然后才能運(yùn)行,這個(gè)邏輯很符合我們平常的開(kāi)發(fā)模式惰蜜。問(wèn)題就在于后半句昂拂,代理類在運(yùn)行時(shí)創(chuàng)建,運(yùn)行時(shí)如何創(chuàng)建代理類抛猖?這是不是很反邏輯格侯?代碼都沒(méi)有,怎么來(lái)根據(jù)我們的需求來(lái)代理真實(shí)的被代理的對(duì)象财著?諸位稍安勿躁联四,我們接下來(lái)會(huì)對(duì)動(dòng)態(tài)代理進(jìn)行詳細(xì)的解釋。
動(dòng)態(tài)代理有兩種實(shí)現(xiàn)方式:
- JDK動(dòng)態(tài)代理
- CGLIB
我們?cè)谶@里主要講解JDK動(dòng)態(tài)代理瓢宦。JDK動(dòng)態(tài)代理底層是通過(guò)Java的反射實(shí)現(xiàn)的碎连,而且只能為接口創(chuàng)建動(dòng)態(tài)代理,而靜態(tài)代理則沒(méi)有這種限制(接口或者抽象類都可以)驮履。下面我們通過(guò)一些代碼來(lái)看一下動(dòng)態(tài)代理的實(shí)現(xiàn)方式鱼辙。同樣廉嚼,我們使用打印日志的這個(gè)例子,方便大家理解倒戏。
package com.blackflagbin.frameanalysis.dynamicproxy;
//日志接口(動(dòng)態(tài)代理不同于靜態(tài)代理怠噪,只能使用接口)
public interface ILogger {
void log();
}
package com.blackflagbin.frameanalysis.dynamicproxy;
//真實(shí)日志類,實(shí)現(xiàn)日志接口
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 {
//被代理對(duì)象(即目標(biāo)對(duì)象)的實(shí)例杜跷,在打印日志這個(gè)例子中對(duì)應(yīng)RealLogger的實(shí)例
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");
//被代理對(duì)象(即目標(biāo)對(duì)象)方法的調(diào)用
return method.invoke(mTarget, args);
} else {
System.out.println("you have no access");
return null;
}
}
//創(chuàng)建實(shí)際的代理對(duì)象
public Object getProxyInstance() {
return Proxy.newProxyInstance(mTarget.getClass().getClassLoader(), mTarget.getClass().getInterfaces(), this);
}
private boolean checkPermission() {
return true;
}
}
package com.blackflagbin.frameanalysis.dynamicproxy;
//測(cè)試類
public class Test {
public static void main(String[] args) {
ProxyHandler proxyHandler = new ProxyHandler(new RealLogger());
ILogger proxy = (ILogger) proxyHandler.getProxyInstance();
proxy.log();
}
}
/*
最終打印結(jié)果:
you have access
show some log
*/
日志接口和真實(shí)日志類沒(méi)什么可說(shuō)的傍念,跟靜態(tài)代理一樣,只不過(guò)因?yàn)閯?dòng)態(tài)代理必須要使用接口所以把抽象類換成了接口葛闷。
動(dòng)態(tài)代理實(shí)現(xiàn)打印日志這個(gè)例子的關(guān)鍵在于ProxyHandler這個(gè)類憋槐。我們知道,在靜態(tài)代理中淑趾,對(duì)原有功能進(jìn)行擴(kuò)展或修改的代碼實(shí)現(xiàn)是在靜態(tài)代理類中定義的阳仔。也就是在ProxyLogger中添加額外的權(quán)限校驗(yàn)方法,并修改打印日志的流程扣泊。那么問(wèn)題來(lái)了近范,在動(dòng)態(tài)代理中,動(dòng)態(tài)代理類是在程序運(yùn)行時(shí)生成的延蟹,我們并沒(méi)有事先聲明一個(gè)動(dòng)態(tài)代理類评矩,這個(gè)對(duì)原有功能進(jìn)行擴(kuò)展或修改的代碼實(shí)現(xiàn)究竟要放在哪里?
問(wèn)題的答案在這個(gè)打印日志的例子里已經(jīng)很明確了阱飘,對(duì)原有功能進(jìn)行擴(kuò)展或修改的代碼實(shí)現(xiàn)放在了一個(gè)類中斥杜,這個(gè)類實(shí)現(xiàn)了InvocationHandler這個(gè)接口。我們通過(guò)對(duì)invoke這個(gè)方法的修改來(lái)修改被代理對(duì)象的方法實(shí)現(xiàn)俯萌,通過(guò)在invoke方法中的method.invoke(mTarget, args)之前或之后插入我們想要的邏輯來(lái)增對(duì)原有功能進(jìn)行擴(kuò)展果录。
在這個(gè)ProxyHandler類中,我們還添加了一個(gè)getProxyInstance()方法來(lái)創(chuàng)建代理類對(duì)象咐熙。通過(guò)Proxy.newProxyInstance(mTarget.getClass().getClassLoader(), mTarget.getClass().getInterfaces(), this)來(lái)創(chuàng)建代理類的實(shí)例是固定的寫(xiě)法弱恒,newProxyInstance需要傳入三個(gè)參數(shù),分別是類加載器棋恼、接口數(shù)組和實(shí)現(xiàn)InvocationHandler的類的實(shí)例對(duì)象返弹。
我們?cè)賮?lái)總結(jié)一下。無(wú)論是靜態(tài)代理還是動(dòng)態(tài)代理爪飘,它們的本質(zhì)都是代理對(duì)象包含一個(gè)被代理對(duì)象的實(shí)例义起,從而對(duì)被代理對(duì)象的原有功能進(jìn)行擴(kuò)展或修改。最大的區(qū)別是代理類的創(chuàng)建時(shí)機(jī)不同师崎,靜態(tài)代理必須在程序運(yùn)行前寫(xiě)好代理類默终;而動(dòng)態(tài)代理的代理類則不需要我們手動(dòng)提前寫(xiě)好,它會(huì)在運(yùn)行時(shí)創(chuàng)建相應(yīng)的代理類。 值得再次強(qiáng)調(diào)的是齐蔽,雖然動(dòng)態(tài)代理不需要我們?cè)诖a中實(shí)現(xiàn)代理類两疚,但是對(duì)原有功能進(jìn)行擴(kuò)展或修改的代碼實(shí)現(xiàn)是必須提前寫(xiě)好的。這個(gè)很好理解含滴,如果開(kāi)發(fā)人員都不寫(xiě)清楚要如何對(duì)原有功能進(jìn)行擴(kuò)展或修改诱渤,計(jì)算機(jī)又怎么知道呢?所以對(duì)原有功能進(jìn)行擴(kuò)展或修改的代碼實(shí)現(xiàn)就必須提前寫(xiě)好谈况,問(wèn)題是這些代碼要放在那里勺美,為了解決這個(gè)問(wèn)題,Java提供了一個(gè)InvocationHandler的接口碑韵,我們只要把相應(yīng)的代碼放到這個(gè)接口的實(shí)現(xiàn)類中即可赡茸。生成的代理對(duì)象在調(diào)用相應(yīng)的方法時(shí),實(shí)際上調(diào)用的是invoke這個(gè)方法泼诱,從而實(shí)現(xiàn)了對(duì)被代理對(duì)象的原有功能進(jìn)行擴(kuò)展或修改坛掠。
最后,我再貼上一些代碼治筒。
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>>
//獲取小區(qū)列表
@GET("${BuildConfig.EXTRA_URL}community/name/list")
fun getCommunityList(): Observable<HttpResultEntity<List<CommunityEntity>>>
//獲取首頁(yè)信息
@GET("${BuildConfig.EXTRA_URL}count/communityStatistic/{communityId}.do")
fun getMainData(@Path("communityId") communityId: Int): Observable<HttpResultEntity<MainEntity>>
//根據(jù)小區(qū)id獲取小區(qū)樓幢列表
@GET("${BuildConfig.EXTRA_URL}community/block/name/list/{communityId}")
fun getBuildingList(@Path("communityId") communityId: Int): Observable<HttpResultEntity<List<BuildingEntity>>>
//根據(jù)樓幢id獲取樓棟下的房間列表
@GET("${BuildConfig.EXTRA_URL}community/block/detail/{blockId}")
fun getRoomList(@Path("blockId") blockId: Int): Observable<HttpResultEntity<BuildingInfoEntity>>
//根據(jù)單元id獲取單元下樓層列表
@GET("${BuildConfig.EXTRA_URL}community/unit/query/{unitId}")
fun getFloorList(@Path("unitId") unitId: Int): Observable<HttpResultEntity<Unit>>
//根據(jù)房間id獲取房間詳情
@GET("${BuildConfig.EXTRA_URL}community/room/detail/{roomId}")
fun getRoomInfo(@Path("roomId") roomId: Int): Observable<HttpResultEntity<RoomInfoEntity>>
//根據(jù)條件查詢,獲取符合條件的居民列表
@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>>
//根據(jù)用戶id獲取關(guān)聯(lián)房屋列表
@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>>
//獲取一級(jí)標(biāo)簽
@GET("${BuildConfig.EXTRA_URL}user/label/parent/list")
fun getFirstLabel(): Observable<HttpResultEntity<List<LabelItemEntity>>>
//獲取二級(jí)標(biāo)簽
@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>>
//獲取門禁狀態(tài)
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}device/selectDeviceStatusCount")
fun getDeviceStatusList(@Field("communityId") communityId: Int): Observable<HttpResultEntity<List<DeviceStatusItem>>>
//根據(jù)條件查詢舷蒲,獲取警情處理規(guī)范列表
@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>>
//根據(jù)條件查詢耸袜,獲取重點(diǎn)人員預(yù)警列表
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}police/person/query")
fun getPersonPreWarningList(
@FieldMap map: Map<String, String>): Observable<HttpResultEntity<PersonPreWarningEntity>>
//根據(jù)條件查詢,獲取觸警人員預(yù)警列表
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}police/person/queryContactPolice")
fun getAttackPreWarningList(
@FieldMap map: Map<String, String>): Observable<HttpResultEntity<PersonPreWarningEntity>>
//根據(jù)條件查詢牲平,獲取重點(diǎn)房屋預(yù)警列表
@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>>>
//開(kāi)門
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}device/operate/1")
fun openLock(
@Field("devicdId") deviceId: String): Observable<HttpResultEntity<Any>>
//更新人員預(yù)警狀態(tài)
@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>>
//更新房屋預(yù)警狀態(tài)
@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>>
//獲取二維碼返回結(jié)果
@FormUrlEncoded
@POST("api/qrcode/parsingcode")
fun getQrCodeResult(
@Field("code") qrcode: String): Observable<HttpResultEntity<String>>
}
這是個(gè)Kotlin寫(xiě)的接口類堤框,用過(guò)Retrofit的同學(xué)應(yīng)該很清楚,使用Retrofit進(jìn)行網(wǎng)絡(luò)請(qǐng)求纵柿,首先就是要?jiǎng)?chuàng)建一個(gè)網(wǎng)絡(luò)請(qǐng)求接口類蜈抓。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://......../")
.build();
ApiService service = retrofit.create(ApiService.class);
看到ApiService service = retrofit.create(ApiService.class)有沒(méi)有很熟悉的感覺(jué)?聰明的小伙伴看到這里應(yīng)該就會(huì)明白了昂儒,沒(méi)錯(cuò)沟使,這其實(shí)就是通過(guò)動(dòng)態(tài)代理創(chuàng)建了一個(gè)代理對(duì)象。我們只是寫(xiě)了一個(gè)網(wǎng)絡(luò)接口類渊跋,里面什么都沒(méi)實(shí)現(xiàn)腊嗡,為什么就可以正確的請(qǐng)求網(wǎng)絡(luò)并包裝返回的數(shù)據(jù)結(jié)果?如果你從上到下把這篇文章看完了拾酝,即使你現(xiàn)在還并不清楚里面具體的代碼細(xì)節(jié)燕少,但有一點(diǎn)你會(huì)非常明確:我們寫(xiě)的ApiService這個(gè)接口類并不具有訪問(wèn)網(wǎng)絡(luò)并包裝返回?cái)?shù)據(jù)結(jié)果的功能,是Retrofit通過(guò)動(dòng)態(tài)代理的方式為我們生成了一個(gè)代理對(duì)象蒿囤,為我們的接口方法擴(kuò)展了網(wǎng)絡(luò)訪問(wèn)的功能客们。理解這一點(diǎn),對(duì)我們后續(xù)的源碼分析非常重要。