kcstrong 2019-06-27
上一节我们讲了一些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动态代理。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通过动态代理的方式为我们生成了一个代理对象,为我们的接口方法扩展了网络访问的功能。理解这一点,对我们后续的源码分析非常重要。