<转>Retrofit源码解析-动态代理

背景

之前一系列的关于Retrofit使用和封装的讲解过后,想必对Retrofit的灵活性和扩展性有何深入的了解,既然如此我们就对于Retrofit内部实现原理来深入的学习,既然要用就要理解怎么用和怎么能用的的更好,不能局限在使用的层面上,接下来的文章从源码的角度去思考和借鉴如何才能写出一个好的开源框架。

原理

Retrofit 2.0是如何进行网络请求的呢?主要是用到了Java的动态代理,所以在深入学习之前,先来了解下Java的动态代理

何为Java动态代理

其实Java动态代理就是设计模式中的代理模式,特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

通俗来说:比如登录之前需要判断用户信息是否完整、请求之前需要配置请求数据等等,动态代理就是来处理这样的需求,让用户只需要关心事件处理

按照代理的创建时期,代理类可以分为两种。

  • 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

  • 动态代理:在程序运行时,运用反射机制动态创建而成。

静态代理

静态代理其实就是按照代理模式,在实现了代理类和委托类之后的调用方式,可以把它看成是代理模式的写法

接口类

/**
 * 接口
 * Created by WZG on 2017/1/19.
 */

public interface HttpRequest {

    /**
     * 请求
     */
    void request();
}

委托类

/**
 * 委托类
 * Created by WZG on 2017/1/19.
 */

public class HttpRequestImpl implements HttpRequest {
    @Override
    public void request() {
        Log.e("tag","-------->htt请求");
    }
}

代理类

/**
 * 代理类-静态
 * Created by WZG on 2017/1/19.
 */

public class HttpRequestProxy implements HttpRequest {

    HttpRequestImpl httpRequest;


    public HttpRequestProxy(HttpRequestImpl httpRequest) {
        this.httpRequest = httpRequest;
    }

    @Override
    public void request() {
        Log.e("tag","静态--------->http请求前");
        httpRequest.request();
        Log.e("tag","静态--------->http请求后");
    }

}

调用

 /*静态代理*/
 HttpRequestImpl request = new HttpRequestImpl();
 HttpRequestProxy proxy = new HttpRequestProxy(request);
        proxy.request();

结果

20170119105129448.png

动态代理

动态代理,字面可理解在代理类中委托类是动态生成的,及时一个动态代理类可处理多个委托类,从而简化代理类的处理。

Java动态代理分为两种

  • JDK动态代理-自带

  • CGlib动态代理-第三方

JDK动态代理

JDK动态代理加载器 Proxy

public class Proxy implements Serializable {
    protected InvocationHandler h;

    protected Proxy(InvocationHandler h) {
        throw new RuntimeException("Stub!");
    }

    public static Class<?> getProxyClass(ClassLoader loader, Class... interfaces) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }

    public static boolean isProxyClass(Class<?> cl) {
        throw new RuntimeException("Stub!");
    }

    public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
}

在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器;

  • Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;

  • Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类;

  • AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。

参数 含义
ClassLoader loader 指被代理的对象
Class[] interfaces 要调用的方法
InvocationHandler h 方法调用时所需要的参数

这里使用第三种AppClassLoader,主要使用newProxyInstance方法去加载

参数 含义
ClassLoader loader 指被代理的对象
Class[] interfaces 要调用的方法
InvocationHandler h 方法调用时所需要的参数
JDK动态代理类处理方法主要是依赖InvocationHandler接口
public interface InvocationHandler {
    Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
}
invoke方法是动态代理类处理的主要方法
参数 含义
Object var1 指被代理的对象
Method var2 要调用的方法
Object[] var3 方法调用时所需要的参数
代理类实现

共用HttpRequest接口,不在重复描述

/**
 * 动态代理-jdk
 * Created by WZG on 2017/1/19.
 */

public class HttpRequestJDKProxy implements InvocationHandler {

    private Object target;
    /**
     * 绑定委托对象并返回一个代理类
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        //取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);   //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        Log.e("tag","jdk--------->http请求前");
        Object  result=method.invoke(target, objects);
        Log.e("tag","jdk--------->http请求后");
        return result;
    }
}
调用
        /*jdk动态代理*/
        HttpRequestJDKProxy jdkProxy = new HttpRequestJDKProxy();
        HttpRequest httpRequest = (HttpRequest) jdkProxy.bind(request);
        httpRequest.request();
结果
20170119110930317.png

CGlib动态代理

源码地址

CGlib动态代理其实是国外一个开源作者写的一个开源库,帮助完善jdk动态代理中

要绑定接口(这是一个缺陷)
Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);

JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理

CGlib代理类

/**
 * cglib动态代理类
 * Created by WZG on 2017/1/19.
 */

public class HttpRequestCglibProxy implements MethodInterceptor {

    /**
     * 创建代理对象
     *
     * @param target
     * @return
     */
    public Object getInstance(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        // 回调方法
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Log.e("tag", "Cglib--------->http请求前");
        Object result= methodProxy.invokeSuper(o, objects);
        Log.e("tag", "Cglib--------->http请求后");
        return result;

    }
}

调用

       /*cglib动态代理*/
        HttpRequestCglibProxy httpRequestCglibProxy=new HttpRequestCglibProxy();
        HttpRequest httpRequestCglib = (HttpRequest) httpRequestCglibProxy.getInstance(new HttpRequestCglibImpl());
        httpRequestCglib.request();

但是由于Android中和java环境的不一,导致在Android项目中其实并不能使用CGlib这种动态代理方式,在java环境中是可以使用这个动态库,所以这里没有显示最后的输出结果,eclipse的java项目是完全可以的(验证过)

动态代理其实主流的使用是在Spring中,最近一段时间才刚刚流行到Android,其实除了CGlib动态代理以外,还有另外一种好用的动态生成思路AOP面向切片
AOP面向切片
想必对OOP再熟悉不过了,那AOP是什么呢?AOP就是把涉及到众多模块的某一类问题进行统一管理,横向的思考需求,然后统一管理处理通用的或者是公用的逻辑,降低项目耦合度,提升效率,简化多余代码,一般AOP结合Aspect(也是Spring技术中大量使用),但是在Android中也有对应的变种AspectJ.

当然由于本文是Retrofit源码解析-动态代理这里就不过多的阐述关于AOP的技术了,有兴趣的同学可以参考JakeWharton-hugo
JakeWharton-hugo

总结

Retrofit源码解析-动态代理其中使用的就是本文中的第二种动态代理JDK动态代理,所以使得Retrofit在扩展性和维护行方面得到很大的提升,但是其实也是有缺点的,因为JDK动态代理里面过多的使用了反射的机制,所以在效率方面是不及CGlib代理处理的,估计也是由于不支持Android项目,所以被迫选择这种方法,个人理解

源码

源码地址

终极封装专栏

RxJava+Retrofit+OkHttp深入浅出-终极封装专栏

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,506评论 25 707
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 3,220评论 2 10
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,184评论 11 349
  • 在创建用户时,需要为新建用户指定一用户组,如果不指定其用户所属的工作组,自动会生成一个与用户名同名的工作组。创建用...
    萤火虫de梦阅读 1,740评论 0 0
  • 前两天,一个朋友说她在布达拉宫看见一个孩子,很像我微信头像那小孩,并且发给我一张照片,问我是不是就是那小孩。 我一...
    帝恶道阅读 353评论 2 0