动态代理是什么
PS:对于动态代理了解的同志可以跳过这一个 part
大家都知道,retrofit 内部应用了动态代理技术,那么动态代理又是什么?以下来自 Oracle 的官方文档 以及我自己的渣翻译
A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface.
在运行时,
动态代理类
实现了一个或者一组接口,目的是,其中任何一个接口的实例的方法调用将会被指派到统一的另一个接口的方法中。(译注:这里的另一个接口方法指的是InvocationHandler
的invoke()
方法)
Thus, a dynamic proxy class can be used to create a type-safe proxy object for a list of interfaces without requiring pre-generation of the proxy class, such as with compile-time tools.
因此,一个动态代理类可以被用于以下场景:为一个或者一组接口创建一个类型安全的代理对象,而不需要预先生成代理类(比如通过编译时工具)。
Method invocations on an instance of a dynamic proxy class are dispatched to a single method in the instance’s invocation handler, and they are encoded with a java.lang.reflect.Method object identifying the method that was invoked and an array of type Object containing the arguments.
使用动态代理类的实例进行的方法调用,会被指派到这个实例的
InvocationHandler
,通过一个java.lang.reflect.Method
的对象指明这个方法和其参数列表(译注:这也就是常见的反射啦)。
翻译得不咋地,各位兄台还是看原文把,或者来张图:
首先我们需要定义一个接口:
第二步是实现接口 InvocationHandler
,这个实现类决定了第一步提到的接口的方法在调用的时候的做法。
第三步是创建出代理类,使用 Proxy.newProxyInstance(ClassLoader, interfaces[], InvocationHandlerImpl)
方法创建一个代理类,这个类在创建过程中会实现传入的所有接口,并且会向生成的代理类注入传入的 InvocationHandlerImpl
。
最后看代理类的接口方法的具体调用过程
具体的过程在图中已经有所描述,传参数啥的仔细看文档即可,
比较重点的就是 InvocationHandler
中覆写的 invoke()
方法,在进行原本的方法调用之前或者之后,可以做点事情。
比如权限验证。假设存在 RoleOne
和 RoleTwo
两种角色,
那么 RoleOneTaskRunner
, RoleTwoTaskRunner
的权限验证是全局的(也就是统一的),在 invoke()
方法中动手脚,可以避免在两个 TaskRunner
的实现类里写重复的代码,同时起到的作用为,将 权限验证机制
和 具体业务逻辑
分开,这也是面向切面编程实现的常用手段。
Retrofit 对动态代理的应用
Retrofit.java
部分
Retrofit 的用法相信大家也很熟悉,全局初始化 Retrofit
实例以后,大体上分为三步:
- 将后台 API 服务声明为接口,将后台 API 接口调用声明为接口方法
- 使用
create()
方法创建接口的实现类 - 使用实现类调用对应的方法实现网络访问
实际上,这个过程就和动态代理的过程一毛一样有木有:
- 将后台 API 服务声明为接口,将后台 API 接口调用声明为接口方法
- 使用
create()
方法创建接口的实现类(其实就是代理类) - 使用实现类调用对应的方法实现网络访问(这里肯定是调用到了
InvocationHandler
的invoke()
方法)
所以,赶紧跳进源码,查看 create()
方法都做了些啥,了解一下 Retrofit 是如何运用动态代理吧。
源码部分, Retrofit.java$create()
1 | public <T> T create(final Class<T> service) { |
分析一下这个方法
1 | Utils.validateServiceInterface(service); |
其实就是检查一下传入的 service 对象是不是一个接口,如果不是接口则抛出异常。
1 | if (validateEagerly) { |
如果 ruguovalidateEagerly
为 true ,则验证接口里的每一个方法,来看 eagerlyValidateMethods()
方法的定义:
Retrofit.java$eagerlyValidateMethods()
1 | private void eagerlyValidateMethods(Class<?> service) { |
首先获取到平台,是接口内部可实现方法的 Java8 还是 Android。
换言之,目前的 Android 平台下的话, !platform.isDefaultMethod(method)
会一直返回 true,也就是会为每一个接口方法加载一个 MethodHandler
。
而加载 MethodHandler
则采用了缓存机制。
Retrofit.java$loadMethodHandler()
1 | MethodHandler<?> loadMethodHandler(Method method) { |
这个 MethodHandler
也很关键,稍后再说,先回到我们的 create()
方法,这个方法最后返回了一个代理类。我们来看关键的 invoke()
方法
1 | if (method.getDeclaringClass() == Object.class) { |
- 如果方法调用来自于
Object
,比如hashCode()
,equals()
等,正常调用 - 如果方法调用是调用接口的内部已经实现的方法,正常调用
- 除去以上两种情况,这里与上面提到的直接使用反射机制的
method.invoke()
不同,这里将method
对象封装到了MethodHandler
中,然后调用了MethodHandler.invoke(args)
这个方法。
MethodHandler.java
部分
所以,我们需要查看 MethodHandler.invoke(args)
都做了啥,在此之前,先看看 MethodHandler
是如何创建的。
1 | static MethodHandler<?> create(Retrofit retrofit, Method method) { |
这里有初始化 CallAdapter
, Converter
等,但不是目前的重点,重点是这一行
1 | RequestFactory requestFactory = RequestFactoryParser.parse(method, responseType, retrofit); |
查看这个 RequestFactoryParser.parse()
方法的定义
1 | static RequestFactory parse(Method method, Type responseType, Retrofit retrofit) { |
这里将 method
对象包含的信息(绝大多数信息是注解上的,如 @GET('url')
, @Path(“id”) String id
)这些注解信息全部解析出来,赋值给了 RequestFactory
对象。
最后再看 MethodHandler.invoke()
方法,这里会把请求的所有信息都交给 callAdapter
处理,最后返回一个 Call
对象,这个 Call
对象我理解为后台 API 调用这个动作,不知道是否有误,如果有误请指出。
1 | Object invoke(Object... args) { |
一个栗子
借用 官方文档 的一个栗子:
定义接口
1 | public interface GitHubService { |
创建代理类
1 | Retrofit retrofit = new Retrofit.Builder() |
所以我们可以将这里的 service
对象理解为
1 | Proxy.newProxyInstance(classLoader, new Class<?>[] {GitHubService.class}, new InvocationHandler(){...}) |
的返回值。
调用代理类的接口方法:
1 | Call<List<Repo>> repos = service.listRepos("octocat"); |
这里的 repos
对象可以理解为
1 | Retrofit.loadMethodHandler(methodListRepos).invoke(new Object[]{"octocat"}); |
的返回值。
至于 repos
后续的处理,涉及太遥远,有心无力啊…因此不在本文讨论的范围内。