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方法之前加入权限校验,从而实现了上述的功能。
动态代理
动态代理与静态代理最大的区别在于动态。那么问题来了,这个动态体现在哪里,又该如何去理解?
这个动态关键在于代理类的创建时机。静态代理中的代理类在我们运行程序之前是必须要存在的,而动态代理中的代理类则是在程序运行时创建的。前半句很好理解,代理类的代码肯定是先存在,然后才能运行,这个逻辑很符合我们平常的开发模式。问题就在于后半句,代理类在运行时创建,运行时如何创建代理类?这是不是很反逻辑?代码都没有,怎么来根据我们的需求来代理真实的被代理的对象?诸位稍安勿躁,我们接下来会对动态代理进行详细的解释。
动态代理有两种实现方式:
- JDK动态代理
- 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>
//登出
@POST("${BuildConfig.EXTRA_URL}account/logout.do")
fun logout(): Observable>
//获取小区列表
@GET("${BuildConfig.EXTRA_URL}community/name/list")
fun getCommunityList(): Observable>>
//获取首页信息
@GET("${BuildConfig.EXTRA_URL}count/communityStatistic/{communityId}.do")
fun getMainData(@Path("communityId") communityId: Int): Observable>
//根据小区id获取小区楼幢列表
@GET("${BuildConfig.EXTRA_URL}community/block/name/list/{communityId}")
fun getBuildingList(@Path("communityId") communityId: Int): Observable>>
//根据楼幢id获取楼栋下的房间列表
@GET("${BuildConfig.EXTRA_URL}community/block/detail/{blockId}")
fun getRoomList(@Path("blockId") blockId: Int): Observable>
//根据单元id获取单元下楼层列表
@GET("${BuildConfig.EXTRA_URL}community/unit/query/{unitId}")
fun getFloorList(@Path("unitId") unitId: Int): Observable>
//根据房间id获取房间详情
@GET("${BuildConfig.EXTRA_URL}community/room/detail/{roomId}")
fun getRoomInfo(@Path("roomId") roomId: Int): Observable>
//根据条件查询,获取符合条件的居民列表
@GET("${BuildConfig.EXTRA_URL}community/resident/list/{pageNo}/{limit}")
fun getSearchedPersonList(
@Path("pageNo") pageNo: Int, @Path(
"limit") limit: Int, @QueryMap map: Map): Observable>
//根据用户id获取关联房屋列表
@GET("${BuildConfig.EXTRA_URL}community/resident/room/list/{userId}")
fun getRelatedRoomList(@Path("userId") userId: Int): Observable>>
//获取楼幢详情
@GET("${BuildConfig.EXTRA_URL}count/blockStatistic/{buildingId}.do")
fun getBuildingDetail(@Path("buildingId") buildingId: Int): Observable>
//获取一级标签
@GET("${BuildConfig.EXTRA_URL}user/label/parent/list")
fun getFirstLabel(): Observable>>
//获取二级标签
@GET("${BuildConfig.EXTRA_URL}user/label/child/list/{parentId}")
fun getSecondLabel(@Path("parentId") parentId: Int): Observable>>
//修改密码
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}account/password/update")
fun changePwd(@Field("password") oldPwd: String, @Field("newPsw") newPwd: String): Observable>
//门禁在线
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}device/query")
fun getDeviceList(@Field("communityId") communityId: Int, @Field("pageNo") pageNo: Int, @Field("limit") limit: Int): Observable>
//获取门禁状态
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}device/selectDeviceStatusCount")
fun getDeviceStatusList(@Field("communityId") communityId: Int): Observable>>
//根据条件查询,获取警情处理规范列表
@GET("${BuildConfig.EXTRA_URL}police/handle/norm/list/{pageNo}/{limit}")
fun getPoliceHandleList(
@Path("pageNo") pageNo: Int, @Path(
"limit") limit: Int, @QueryMap map: Map): Observable>
//根据条件查询,获取重点人员预警列表
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}police/person/query")
fun getPersonPreWarningList(
@FieldMap map: Map): Observable>
//根据条件查询,获取触警人员预警列表
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}police/person/queryContactPolice")
fun getAttackPreWarningList(
@FieldMap map: Map): Observable>
//根据条件查询,获取重点房屋预警列表
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}police/room/query")
fun getRoomPreWarningList(
@FieldMap map: Map): Observable>
//获取行为轨迹
@GET("${BuildConfig.EXTRA_URL}user/info/live/history/{userId}")
fun getMovePath(
@Path("userId") userId: String): Observable>>
//开门
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}device/operate/1")
fun openLock(
@Field("devicdId") deviceId: String): Observable>
//更新人员预警状态
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}police/person/updStatu")
fun updatePersonPreWaning(
@Field("id") id: String, @Field("statu") status: Int, @Field("processContent") processContent: String): Observable>
//更新房屋预警状态
@FormUrlEncoded
@POST("${BuildConfig.EXTRA_URL}police/room/updStatu")
fun updateRoomPreWaning(
@Field("id") id: String, @Field("statu") status: Int, @Field("processContent") processContent: String): Observable>
//获取二维码返回结果
@FormUrlEncoded
@POST("api/qrcode/parsingcode")
fun getQrCodeResult(
@Field("code") qrcode: String): Observable>
}
这是个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通过动态代理的方式为我们生成了一个代理对象,为我们的接口方法扩展了网络访问的功能。理解这一点,对我们后续的源码分析非常重要。