Retrofit2 动态代理下的分析

Retrofit2 目前已经越来越主流稳定了,它终于完全抛弃了其它的网络库而是使用了OkHttp3作为依赖,功能也更加插件化了。经常听到动态代理这个词而不知所云,现在知识水平高了,分析一下,以飨(xiǎng)读者。

如果用一个词来概括Retrofit的话,那么“拼装”就是最准确的,它借鉴了以前服务器编程中的动态代理技术,通过接口在运行时生成字节码;接着通过注解拼装HTTP请求;最后包装了OkHttp,实现了对Rx、线程的adaption。

本文主要是过程(practice),而不是教程(tutorial),所以希望各位下载代码实践。

本文基于retrofit:2.0.0-beta3jdk1.8.0_05进行分析


你将学到

  1. 代理的一些简介
  2. 定义的接口在动态代理使用前与使用后的对比
  3. Retrofit如何完成interface(方法,注解与泛型)到Okhttp请求对象的转换(待填坑)
  4. Retrifit如何使用Okhttp并在回调时进行线程切换,已经另开文章写

准备工具

  1. 对字节码进行分析的反编译工具
  2. 对字节码保存的字节码保存工具类,用这个可以导出class文件,当然你也可以在jvm

     ProxyUtils.saveProxyClass("123.class", "Github", new Class[] { GitHub.class });
  3. Retrofit2的源码,注意它是基于maven的,最好用Idea导入

     git clone --depth=1 https://github.com/square/retrofit.git
  4. 还不知道Retrofit怎么用?先看看Retrofit解析JSON数据

1. 什么是代理

代理是一种设计模式,分为静态代理与动态代理。

没有代理前,调用是这样的:

  • 调用者
  • 业务细节实现

使用后,它们的调用顺序是这样的:

  • 调用者
  • 业务逻辑(一般是抽象方法或者是接口)
  • 业务细节实现

通过在中间加了一层代理,将业务逻辑与实现细节分离,方便了上层开发者。

1.1. 举例

1.1.1. 办手续

我需要去有关部门办很多手续,但是不想来回跑,于是找到了一家中介。

<-- 次性提供中介要求的几个证件 --> 中介
中介 <-- 进行繁琐的填表跑腿工作 --> 有关部门

通过代理,我不用了解跑腿的细节,就可以完成业务,这个就是代理的优点,面向业务而忽略细节。当然这个是有代价的(比如需要交跑腿费),在编码中,代理类加大了编码量。

1.1.2. 上外网

我需要访问国外的网站,但是速度很慢,于是找到了一个服务商。

<-- 一键配置pac --> 中转服务器
中转服务器 <-- 进行繁琐的开发、运维工作 --> 外网

通过代理,我只用复制一个pac,就可以完成业务,而不用去想怎么与防火墙斗智斗勇,同样这个是需要代价的

1.2. 理论

1.2.1. 静态代理

通过用户手动编码或者在编译时自动生成。

在 AppCompatActivity 中的AppCompatDelegate 抽象类就是静态代理,而抽象类的实例化是在静态语句块 static{} 区中根据版本号实现的,这样写可以避免 Activity 本身的业务过于冗余,同时将抽象类(也可能是接口)与实现类相分离,这样上层开发者可以专注于业务,而不用管具体的实现。

AppCompatActivity - 委任 - 委任实现

在Android中的IPC访问中,如果需要跨进程访问方法,需要使用AIDL通信,当调用者调用远程方法时,调用的是 Stub 类,也就是远程进程的桩,接着这个桩通过 ASHMEM(匿名共享内存) 进行转发,最后方法的实现还是在远程服务器中。

调用者  - Stub接口(由AIDL生成) - 远程服务

上面的 stub, delegate 都是静态代理,本身是抽象类的,让调用者实现了面向接口业务编程。

1.2.2. 动态代理

动态代理是java中的字节码生成技术。通过接口生成代理类,并将代理类的实现交给 InvocationHandler 作为具体的实现。

  1. Retrofit中为了简化复杂的网络请求,通过动态代理的去处理解析与拼装。
  2. 希望给某个方法在执行的过程中添加权限控制或者打log,这时我们可以用动态代理生成后在handler中装饰多余的业务,从而保存原有的方法专注于业务本身(即AOP编程),例子在这里
  3. 在运行时“欺上瞒下”,比如360的插件化技术的实现

动态代理的过程

动态代理借助java的反射特性,通过易写的接口与注解,帮助用户自动生成实际的对象,接口中方法真正的调用交给InvocationHandler。

Input interface
Proxy.newProxyInstance 对接口进行包装,内部方法由handler委任
Output interface(with InvocationHandler)

当用户调用生成接口中的方法时,实际上调用了InvocationHandler中的invoke方法

现在我们将动态代理前的接口文件,与代理后的运行生成的反编译后的文件进行对比

生成前

官方的示例接口

public interface GitHub {
  @GET("/repos/{owner}/{repo}/contributors") Call<List<Contributor>> contributors(
      @Path("owner") String owner, @Path("repo") String repo);
}

官方对接口的调用

Call<List<Contributor>> call = github.contributors("square", "retrofit");

代理生成中

它在Retrofit中的create中作为参数传入,通过调用下面方法读取接口信息并在运行时生成字节码并用classloader加载

Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
  new InvocationHandler() {
    private final Platform platform = Platform.get();

    @Override public Object invoke(Object proxy, Method method, Object... args)
         .....
         //开始处理注解并拼装成OkHttp的call
      return loadMethodHandler(method).invoke(args);
    }
  });
}

生成后

这是动态代理生成后用工具dump出并反编译的class(下面省略了Object自带的方法,以及大量的异常捕捉)

public final class Github extends Proxy implements SimpleService.GitHub {
  private static Method m0;//自带的hashCode方法
  private static Method m1;//自带的equals方法
  private static Method m2;//自带的toString
  private static Method m3;//真正的接口中的方法

  static {
    try {
    //省略Object自带方法的初始化
      m0,m1,m2 = ...
      //接口中真正的方法
      m3 = Class.forName("com.example.retrofit.SimpleService$GitHub")
          .getMethod("contributors",
              new Class[] { Class.forName("java.lang.String"), Class.forName("java.lang.String") });
    } catch (Throwable e) {
      throw new NoSuchMethodError(e.getMessage());
    } catch (Throwable e2) {
      throw new NoClassDefFoundError(e2.getMessage());
    }
  }

  public Github(InvocationHandler invocationHandler) {
    super(invocationHandler);
  }

    //这里的参数实际上就是用户输入的"square", "retrofit"
  public final Call contributors(String str, String str2) {
    RuntimeException e;
    try {
    //this.h是InvocationHandler,也就是上文重写的Handler
    //可以看出实际调用的是handler的`invoke`方法
      return (Call) this.h.invoke(this, m3, new Object[] { str, str2 });
    } catch (Throwable e) {
      throw e;
    } 
  }

  ....Object自带方法的代理...
}

可以看出InvocationHandler是最重要的修饰,Handler中的invoke进行了实际处理,而这些是我们重写过的,至于具体怎么处理的,这些坑后续再补。

动态代理的原理

代理生成的过程使用了sun包中的闭源方法,反编译大致看了一下,先把接口通过ProxyGenerator.generateProxyClass()根据Class文件的规范拼装生成了byte[]字节码,接着用native方法defineClass0()转换为对象,可以看出动态代理本质上是生成大量样板代码的过程。相比于静态代理,动态代理可以减少编写代理类(比如XXXImpl)的工作量。

动态代理(反射)是否降低了性能?

不会,经过测试。retrofit构造与动态代理加起来的时间只有 0.65ms,相对于 16ms ,相比于网络请求,完全不是一个数量级。如果你是强迫症的话,可以让它单例化。

Refference

  1. https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/
  2. http://a.codekk.com/detail/Android/Caij/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8B%20Java%20%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86
  3. http://docs.oracle.com/javase/tutorial/reflect/
  4. http://tutorials.jenkov.com/java-reflection/generics.html

  1. http://www.ibm.com/developerworks/cn/java/j-jtp08305.html
  2. http://www.jianshu.com/p/a56c61da55dd

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值