If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
create
方法就是返回了一个Proxy.newProxyInstance
动态代理对象。那么问题来了...
动态代理是个什么东西?
看Retrofit代码之前我知道Java动态代理是一个很重要的东西,比如在Spring框架里大量的用到,但是它有什么用呢?
Java动态代理就是给了程序员一种可能:当你要调用某个Class的方法前或后,插入你想要执行的代码
比如你要执行某个操作前,你必须要判断这个用户是否登录,或者你在付款前,你需要判断这个人的账户中存在这么多钱。这么简单的一句话,我相信可以把一个不懂技术的人也讲明白Java动态代理是什么东西了。
为什么要使用动态代理
你看上面代码,获取数据的代码就是这句:
Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");
上面api
对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi
接口的implements
产生的对象,当api
对象调用getAuthor
方法时会被动态代理拦截,然后调用Proxy.newProxyInstance
方法中的InvocationHandler
对象,它的invoke
方法会传入3个参数:
- Object proxy: 代理对象,不关心这个
- Method method:调用的方法,就是
getAuthor
方法 - Object... args:方法的参数,就是
"qinchao"
而Retrofit关心的就是method
和它的参数args
,接下去Retrofit就会用Java反射获取到getAuthor
方法的注解信息,配合args
参数,创建一个ServiceMethod
对象
ServiceMethod
就像是一个中央处理器,传入Retrofit
对象和Method
对象,调用各个接口和解析器,最终生成一个Request
,包含api 的域名、path、http请求方法、请求头、是否有body、是否是multipart等等。最后返回一个Call
对象,Retrofit2中Call接口的默认实现是OkHttpCall
,它默认使用OkHttp3作为底层http请求client
使用Java动态代理的目的就要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送
3 Retrofit的源码分析
想要弄清楚Retrofit的细节,先来看一下Retrofit源码的组成:
- 一个
retrofit2.http
包,里面全部是定义HTTP请求的Java注解,比如GET
、POST
、PUT
、DELETE
、Headers
、Path
、Query
等等 - 余下的
retrofit2
包中几个类和接口就是全部retrofit的代码了,代码真的很少,很简单,因为retrofit把网络请求这部分功能全部交给了OkHttp了
Retrofit接口
Retrofit的设计非常插件化而且轻量级,真的是非常高内聚而且低耦合,这个和它的接口设计有关。Retrofit中定义了4个接口:
Callback<T>
这个接口就是retrofit请求数据返回的接口,只有两个方法
void onResponse(Response<T> response);
void onFailure(Throwable t);
Converter<F, T>
这个接口主要的作用就是将HTTP返回的数据解析成Java对象,主要有Xml、Gson、protobuf等等,你可以在创建Retrofit
对象时添加你需要使用的Converter
实现(看上面创建Retrofit对象的代码)
Call<T>
这个接口主要的作用就是发送一个HTTP请求,Retrofit默认的实现是OkHttpCall<T>
,你可以根据实际情况实现你自己的Call类,这个设计和Volley的HttpStack
接口设计的思想非常相似,子类可以实现基于HttpClient
或HttpUrlConnetction
的HTTP请求工具,这种设计非常的插件化,而且灵活
CallAdapter<T>
上面说到过,CallAdapter
中属性只有responseType
一个,还有一个<R> T adapt(Call<R> call)
方法,这个接口的实现类也只有一个,DefaultCallAdapter
。这个方法的主要作用就是将Call
对象转换成另一个对象,可能是为了支持RxJava才设计这个类的吧
Retrofit的运行过程
上面讲到ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);
代码返回了一个动态代理对象,而执行Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");
代码时返回了一个OkHttpCall
对象,拿到这个Call
对象才能执行HTTP请求
上面api
对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi
接口的implements
产生的对象,当api
对象调用getAuthor
方法时会被动态代理拦截,然后调用Proxy.newProxyInstance
方法中的InvocationHandler
对象, 创建一个ServiceMethod
对象
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
创建ServiceMethod
刚才说到,ServiceMethod
就像是一个中央处理器,具体来看一下创建这个ServiceMethod
的过程是怎么样的
第一步,获取到上面说到的3个接口对象:
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
responseConverter = createResponseConverter();
第二步,解析Method的注解,主要就是获取Http请求的方法,比如是GET还是POST还是其他形式,如果没有,程序就会报错,还会做一系列的检查,比如如果在方法上注解了@Multipart
,但是Http请求方法是GET,同样也会报错。因此,在注解Java方法是需要严谨
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
第三步,比如上面api中带有一个参数{user}
,这是一个占位符,而真实的参数值在Java方法中传入,那么Retrofit会使用一个ParameterHandler
来进行替换:
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
最后,ServiceMethod
会做其他的检查,比如用了@FormUrlEncoded
注解,那么方法参数中必须至少有一个@Field
或@FieldMap
执行Http请求
之前讲到,OkHttpCall
是实现了Call
接口的,并且是真正调用OkHttp3
发送Http请求的类。OkHttp3
发送一个Http请求需要一个Request
对象,而这个Request
对象就是从ServiceMethod
的toRequest
返回的
总的来说,OkHttpCall
就是调用ServiceMethod
获得一个可以执行的Request
对象,然后等到Http请求返回后,再将response body传入ServiceMethod
中,ServiceMethod
就可以调用Converter
接口将response body转成一个Java对象
结合上面说的就可以看出,ServiceMethod
中几乎保存了一个api请求所有需要的数据,OkHttpCall
需要从ServiceMethod
中获得一个Request
对象,然后得到response后,还需要传入ServiceMethod
用Converter
转换成Java对象
你可能会觉得我只要发送一个HTTP请求,你要做这么多事情不会很“慢”吗?不会很浪费性能吗?
我觉得,首先现在手机处理器主频非常高了,解析这个接口可能就花1ms可能更少的时间(我没有测试过),面对一个HTTP本来就需要几百ms,甚至几千ms来说不值得一提;而且Retrofit会对解析过的请求进行缓存,就在Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
这个对象中
如何在Retrofit中使用RxJava
由于Retrofit设计的扩展性非常强,你只需要添加一个CallAdapter
就可以了
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(ProtoConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
上面代码创建了一个Retrofit
对象,支持Proto和Gson两种数据格式,并且还支持RxJava
4 最后
Retrofit非常巧妙的用注解来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了Java动态代理的方式,动态的将这个接口的注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求
Retrofit的功能非常多的依赖Java反射,代码中其实还有很多细节,比如异常的捕获、抛出和处理,大量的Factory设计模式(为什么要这么多使用Factory模式?)
Retrofit中接口设计的恰到好处,在你创建Retrofit
对象时,让你有更多更灵活的方式去处理你的需求,比如使用不同的Converter
、使用不同的CallAdapter
,这也就提供了你使用RxJava来调用Retrofit的可能
我也慢慢看了Picasso和Retrofit的代码了,收获还是很多的,也更加深入的理解面向接口的编程方法,这个写代码就是好的代码就是依赖接口而不是实现最好的例子
好感谢开源的世界,让我能读到大牛的代码。我一直觉得一个人如果没有读过好的代码是不太可能写出好代码的。什么是好的代码?像Picasso和Retrofit这样的就是好的代码,扩展性强、低耦合、插件化
作者:白瓦力
链接:http://www.jianshu.com/p/c1a3a881a144
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。