Retrofit源码分析二 代理模式

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方法之前加入权限校验,从而实现了上述的功能。

动态代理

动态代理与静态代理最大的区别在于动态。那么问题来了,这个动态体现在哪里,又该如何去理解?
这个动态关键在于代理类的创建时机。静态代理中的代理类在我们运行程序之前是必须要存在的,而动态代理中的代理类则是在程序运行时创建的。前半句很好理解,代理类的代码肯定是先存在,然后才能运行,这个逻辑很符合我们平常的开发模式。问题就在于后半句,代理类在运行时创建,运行时如何创建代理类?这是不是很反逻辑?代码都没有,怎么来根据我们的需求来代理真实的被代理的对象?诸位稍安勿躁,我们接下来会对动态代理进行详细的解释。
动态代理有两种实现方式:

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

阅读 547发布于 2018-04-17

推荐阅读
目录