前言
通常我不喜欢去写分析源码类的文章,流水线式的分析 枯燥乏味,但读完Retrofit
源码后让我有了改变这种想法的冲动~~
一般来讲读源码的好处有两点:
- 熟悉代码设计流程,使用过程碰到问题可以更快速解决。说实话仅这一点无法激起我读源码的兴趣,毕竟以正确的姿态使用一个
优秀
的框架不应该出现这种问题。
- 一个优秀的框架必须要保证
易用性、扩展性
,所以作者定会引入大量的思考进行设计,如若我们能吸收一二,那何尝不是与作者进行了一次心灵交互呢!
今天我将带着我的理解,尝试从设计者的角度分析Retrofit
原理,相信你认真读完再加以思考,当再被面试官问Retrofit
时你的答复或许会让他眼前一亮
提示:Retrofit基于2.9.0。文中贴的源码可能会有部分缺失,这是我刻意为之,目的在于筛选掉无用信息增强可读性
目录
- 1. 什么是REST ful API?
- 2. 为什么将请求设置为(接口+注解)形式?
- 2.1 迪米特法则和门面模式
- 2.2 为什么通过门面模式设计ApiService?
- 3. 动态代理其实不是工具
- 3.1 Retrofit构建
- 3.2 何为动态代理?
- 3.3 动态代理获取ApiService
- 4. ReturnT、ResponseT做一次适配的意义何在?
- 4.1 创建HttpServiceMethod
- 4.2 如何管理callAdapter、responseConverter?
- 4.3 发起请求
1. 什么是REST ful API?
一句话概括REST ful API
:在我们使用HTTP
协议做数据传输时应当遵守HTTP
的规矩,包括请求方法
、资源类型
、Uri格式
等等..
不久前在群里看到某小伙伴提出一个问题:“应后端要求需要在GET
请求加入Body
但Retrofit
中GET
请求添加Body
会报错,如何解决?” 一时间讨论的好不热闹,有让把Body
塞到Header
里的,有让自定义拦截器
、也有人直接怂恿改源码…但问题的本质不是后端先违反规则在先吗?两个人打架总不能把挨打的抓起来吧。
俗话说无规矩不成方圆,面对以上这种情况应当让错误方去修改,因为所有人都知道GET
没有Body
,否则一旦其他人接手你的代码很容易被搞懵。
Retrofit
对REST ful API
的兼容做的很优秀,不符合规范直接给你报错,强行规范你的代码。所以你们公司正在使用REST ful API
而Retrofit
将是你的不二选择
2. 为什么将请求设置为(接口+注解)形式?
该小节为前置知识
2.1 迪米特法则和门面模式
- 迪米特法则:也称之为
最小知道原则
,即模块之间尽量减少不必要的依赖,即降低模块间的耦合性。
- 门面模式:基于
迪米特法则
拓展出来的一种设计模式
,旨在将复杂的模块/系统
访问入口控制的更加单一。 举个例子:现要做一个获取图片功能,优先从本地缓存获取,没有缓存从网络获取随后再加入到本地缓存,假如不做任何处理,那每获取一张图片都要写一遍缓存逻辑,写的越多出错的可能就越高,其实调用者只是想获取一张图片而已,具体如何获取他不需要关心。此时可以通过门面模式将缓存功能做一个封装,只暴露出一个获取图片入口,这样调用者使用起来更加方便而且安全性更高。其实函数式编程也是门面模式
的产物
2.2 为什么通过门面模式设计ApiService?
用Retrofit做一次请求大致流程如下:
interface ApiService {
/**
* 获取首页数据
*/
@GET("/article/list/{page}/json")
suspend fun getHomeList(@Path("page") pageNo: Int)
: ApiResponse<ArticleBean>
}
//构建Retrofit
val retrofit = Retrofit.Builder().build()
//创建ApiService实例
val apiService =retrofit.create(ApiService::class.java)
//发起请求(这里用的是suspend会自动发起请求,Java中可通过返回的call请求)
apiService.getHomeList(1)
复制代码
然后通过Retrofit
创建ApiService
类型实例调用对应方法即可发起请求。乍一看感觉很普通,但实际上Retrofit
通过这种模式(门面模式)
帮我们过滤掉了很多无用信息
如果直接使用OkHttp
,当在构造Request
时要做很多繁琐的工作,最要命的是Request
可能在多处被构造(ViewModel、Repository…),写的越分散出错时排查的难度就越高。而Retrofit
通过注解的形式将Request
需要的必要信息全依附在方法上(还是个抽象方法,尽量撇除一切多余信息),作为使用者只需要调用对应方法即可实现请求。至于如何解析、构造、发起请求
Retrofit内部会做处理,调用者不想也不需要知道,
所以Retrofit
通过门面模式
帮调用者屏蔽了一些无用
信息,只暴露出唯一入口,让调用者更专注于业务开发。像我们常用的Room
、GreenDao
也使用了这种模式
3. 动态代理其实不是工具
看过很多
Retrofit
相关的文章,都喜欢上来就抛动态代理
,关于为什么用只字不提,搞的Retrofit动态代理
像是一个工具(框架)
一样,殊不知它只是代理模式
思想层面的一个产物而已。本小结会透过Retrofit
看动态代理本质,帮你解除对它的误解
3.1 Retrofit构建
Retrofit构建如下所示:
Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(ApiConstants.BASE_URL)
.build()
复制代码
很典型的构建者模式
,可以配置OkHttp
、Gson
、RxJava
等等,最后通过build()
做构建操作,跟一下build()
代码:
#Retrofit.class
public Retrofit build() {
//1.CallAdapter工厂集合
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
//2.Converter工厂集合
List<Converter.Factory> converterFactories =
new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());
return new Retrofit(
callFactory,
baseUrl,
unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories),
callbackExecutor,
validateEagerly);
}
复制代码
将一些必要信息注入到Retrofit
并创建返回。注释1、2
处两个集合非常重要,这里先埋个伏笔后面我们再回来看
3.2 何为动态代理?
什么是代理模式?
代理模式概念非常简单,比如
A
想做一件事可以让B
帮他做,这样做的好处是什么?
下面通过一个例子简要说明。需求:每一次本地数据库CRUD
都要做一次上报
最简单粗暴的方式就是每次CRUD时都单独做一次记录,代码如下
//业务层方法test1
fun test1{
//数据库插入操作
dao.insert()
//上报
post()
}
//业务层方法test2
fun test2(){
//数据库更新操作
dao.update()
//上报
post()
}
复制代码
以上这种方式存在一个问题:
上报
操作本身与具体业务无关,一旦需要对上报
进行修改,那就可能影响到业务,进而可能造成不可预期的问题产生
面对以上问题可以通过代理模式
完美规避,改造后的代码如下:
class DaoProxy(){
//数据库插入操作
fun insert(){
dao.insert()
//上报
post()
}
//数据库更新操作
fun update(){
dao.update()
//上报
post()
}
}
//业务层方法test1
fun test1{
//数据库插入操作
daoProxy.insert()
}
//业务层方法test2
fun test2(){
//数据库更新操作
daoProxy.update()
}
复制代码
新增一个代理类DaoProxy
,将dao
以及上报
操作在代理类中执行,业务层直接操作代理对象
,这样就将上报
从业务层抽离出来,从而避免业务层改动
带来的问题。实际使用代理模式
时应遵守基于接口而非实现编程思想
,但文章侧重于传授思想,规范上可能欠缺
此时还有一个问题,每次CRUD
都会手动做一次上报操作,这显然是模版代码,如何解决?下面来看动态代理:
什么是动态代理?
java
中的动态代理就是在运行时通过反射为目标对象做一些附加操作,代码如下:
class DaoProxy() {
//创建代理类
fun createProxy(): Any {
//创建dao
val proxyAny = Dao()
val interfaces = proxyAny.javaClass.interfaces
val handler = ProxyHandler(proxyAny)
return Proxy.newProxyInstance(proxyAny::class.java.classLoader, interfaces, handler)
}
//代理委托类
class ProxyHandler(private val proxyObject:Any): InvocationHandler {
//代理方法,p1为目标类方法、p2为目标类参数。调用proxyObject任一方法时都会执行invoke
override fun invoke(p0: Any, p1: Method, p2: Array<out Any>): Any {
//执行Dao各个方法(CRUD)
val result = p1.invoke(proxyObject,p2)
//上报
post()
return result
}
}
}
//此处规范上应该使用基于接口而非实现编程。如果要替换Dao通过接口编程可提高扩展性
val dao:Dao = DaoProxy().createProxy() as Dao
dao.insert()
dao.update()
复制代码
其中Proxy
是JDK
中用于创建动态代理的类,InvocationHandler
是一个委托类, 内部的invoke(代理方法)
方法会随着目标类(Dao)
任一方法的调用而调用,所以在其内部实现上报操作即可消除大量模版代码。
动态代理
与静态代理
核心思想一致,区别是动态代理
可以在运行时通过反射
动态创建一个切面(InvocationHandler#invoke)
,用来消除模板代码。喜欢思考的同学其实已经发现,代理模式
符合面向切面编程(AOP)
思想,而代理类就是切面
3.3 动态代理获取ApiService
2.2小节有提到可以通过retrofit.create()
创建ApiService
,跟一下retrofit
的create()
#Retrofit.class
public <T> T create(final Class<T> service) {
//第一处
validateServiceInterface(service);
return (T) Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
//第二处
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
...
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
复制代码
create()
大致可以分为两部分:
- 第一部分为
validateServiceInterface()
内容,用来验证ApiService
合法性,比较简单就不多描述,感兴趣的同学可自行查看。 - 第二部分就是
invoke()
,通过3.2
小节可知这是一个代理方法,可通过调用ApiService
中的任一方法执行,其中参数method
和args
代表ApiService
对应的方法和参数。返回值中有一个isDefaultMethod
,这里如果是Java8的默认方法直接执行,毕竟我们只需要代理ApiService
中方法即可。经过反复筛选最后重任落在了loadServiceMethod
,这也是Retrofit
中最核心的一个方法,下面我们来跟一下
#Retrofit.class
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
复制代码
大致就是对ServiceMethod
做一个很常见的缓存操作,这样做的目的是为了提升运行效率,毕竟创建一个ServiceMethod
会用到大量反射。创建ServiceMethod
对象是通过其静态方法parseAnnotations
实现的,再跟一下这个方法:
#ServiceMethod.class
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
//第一步
RequestFactory requestFactory =
RequestFactory.parseAnnotations(retrofit, method);
Type returnType = method.getGenericReturnType();
...
//第二步
return HttpServiceMethod.parseAnnotations(retrofit,
method, requestFactory);
}
复制代码
第一步:
通过RequestFactory
的parseAnnotations()
解析method(ApiService的method)
中的注解信息,具体代码很简单就不再贴了。不过需要注意这一步只是解析注解并保存在RequestFactory
工厂中,会在请求时再通过RequestFactory
将请求信息做拼装。
第二步:
调用HttpServiceMethod
的parseAnnotations
创建ServiceMethod
,这个方法很长并且信息量很大,下一小节我再详细描述,此处你只需知道它做了什么即可。其实到这方法调用链已经很绕了,我先帮大家捋一下 HttpServiceMethod
其实是ServiceMethod
的子类,Retrofit
动态代理里面的loadServiceMethod
就是HttpServiceMethod
类型对象,最后来看一下它的invoke()
方法。
#HttpServiceMethod.class
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
复制代码
创建了一个OkHttpCall
实例,它内部其实就是对OkHttp
的一系列操作,这里先按住不表后面我会再提到。把关注点切到返回值,返回的Call
对象没做任何操作,而是传入到adapter()
方法一并返回来,字面意思应该是一个适配操作,那究竟如何适配?这里再埋一个伏笔与3.1
结尾相呼应,下一小节我们再一一揭开。
动态代理
讲完了,那么它解决了什么问题?
-
假如不使用
代理模式
,那关于ApiService
中方法注解解析的操作势必会浸入到业务当中,一旦对其修改就有可能影响到业务,其实也就是也违背了我们前面所说的门面模式
和迪米特法则
,通过代理模式做一个切面操作(AOP)
可以完美规避了这一问题。可见这里的门面模式
和代理模式
是相辅相成的 -
Retrofit
事先都不知道ApiService
方法数量,就算知道也避免不了逐一解析而产生大量的模版代码,此时可通过引入动态代理
在运行时动态解析 从而解决这一问题。
4. ReturnT、ResponseT做一次适配的意义何在?
ResponseT、ReturnT是 Retrofit 对响应数据类型和返回值类型的简称
4.1 创建HttpServiceMethod
上一小节我们跟到了adapter()
,这是一个抽象方法,其实现类是通过HttpServiceMethod
的parseAnnotations
创建的,继续跟下去:
#HttpServiceMethod.class
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
//1.获取adapterType,默认为method返回值类型
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType =
Utils.getParameterLowerBound(
0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
} else {
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
//2.创建CallAdapter
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
//3.创建responseConverter
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
//4.创建HttpServiceMethod类型具体实例
if (!isKotlinSuspendFunction) {
return new HttpServiceMethod.CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}
//兼容kotlin suspend方法
else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>)
new HttpServiceMethod.SuspendForResponse<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>)
new HttpServiceMethod.SuspendForBody<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
}
复制代码
- 注释1:获取
adapterType
,这里的adapter
指的是Retrofit
构建时通过addCallAdapterFactory()
添加的类型,如果添加的是RxJava
那adapterType
便是Observable
。默认是method
返回值,同时也会做kotlin suspend
适配- 注释2:创建callAdapter,暂时掠过,下面详细描述
- 注释3:创建responseConverter,暂时掠过,下面详细描述
- 注释4:这里会创建具体的
HttpServiceMethod
类型实例,总共有三种类型CallAdapted
、SuspendForResponse
、SuspendForBody
,第一种为默认类型,后两种可兼容kotlin suspend。内部主要做的事情其实很简单,就是通过内部的adapter()
调用callAdapter``adapter()
,具体代码就不贴了,感兴趣的自行查看
4.2 如何管理callAdapter、responseConverter?
创建创建callAdapter
#HttpServiceMethod.class
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
...
}
复制代码
通过retrofit#callAdapter()
获取CallAdapter
,继续跟
#Retrofit.class
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
public CallAdapter<?, ?> nextCallAdapter(
@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
//通过returnType在callAdapterFactories获取adapter工厂,再get adapter
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
...
}
复制代码
先通过returnType
在callAdapterFactories
获取adapter工厂,再通过工厂get()
获取CallAdapter
实例。callAdapterFactories
是3.1
结尾build()
中初始化的,通过platform
添加默认类型,也可以通过addCallAdapterFactory()
添加RxJava
之类的适配器类型。
这里用到了两个设计模式适配器
跟策略
适配器模式
返回的CallAdapter
其实就是对Call<T>
的适配器,假如你想让Retrofit
配合RxJava
使用,常规方式只能在业务中单独创建Observable
并与Call
融合,关于Observable
与Call
融合(适配)其实是与业务无关的,此时可以引入适配器模式将Call
适配成Observable
,将适配细节从业务层挪到Retrofit
内部,符合迪米特法则
策略模式
通过ReturnT
获取对应的CallAdapter
,如果ReturnT
是Call<T>
那获取的是DefaultCallAdapterFactory
创建的实例,如果如果ReturnT
是Observable<T>
则获取的是RxJava2CallAdapterFactory
创建的实例。假如想新增一种适配器只需明确ReturnT
,创建对应工厂再通过addCallAdapterFactory
添加即可,Retrofit
会通过ReturnT
自动寻找对应CallAdapter
,符合开闭原则(扩展开放)
创建responseConverter
关于responseConverter
其实是做数据转换的,可以将ResponseT
适配成我们想要的数据类型,比如Gson
解析只需通过addConverterFactory
添加GsonConverterFactory
创建的Converter
实例即可
具体添加、获取流程与CallAdapter
基本一致,感兴趣的同学可自行查看
4.3 发起请求
到上一小结我们已经创建了所有需要的内容,再回到HttpServiceMethod
的invoke
,这里会将OkHttpCall
传入到adapt
执行并返回,HttpServiceMethod
的实现类的adapter
会执行对应CallAdapter
的adapter
我们就取默认的CallAdapter
即DefaultCallAdapterFactory
通过get
获取的CallAdapter
,代码如下:
DefaultCallAdapterFactory.class
public @Nullable CallAdapter<?, ?> get(
return new CallAdapter<Object, Call<?>>() {
@Override
public Type responseType() {
return responseType;
}
@Override
public Call<Object> adapt(Call<Object> call) {
return executor == null ? call : new DefaultCallAdapterFactory.ExecutorCallbackCall<>(executor, call);
}
};
}
复制代码
内部adapt
即ApiService method
最终返回的ExecutorCallbackCall
是OkHttpCall
装饰类,最后可通过OkHttpCall
的execute
发起请求,代码如下:
#OkHttpCall.class
public Response<T> execute() throws IOException {
okhttp3.Call call;
...
return parseResponse(call.execute());
}
复制代码
OkHttp
常规操作,再把关注点放到onResponse
的parseResponse
#OkHttpCall.class
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
...
T body = responseConverter.convert(catchingBody);
...
return Response.success(body, rawResponse);
}
复制代码
responseConverter
会对Body
做一个适配,如果addConverterFactory
添加了GsonConvert
那解析操作就会在此处进行
至此Retrofit
全部流程分析完毕
综上所述
- Retrofit通过REST ful API从范式层面约束代码
- 通过门面模式设计ApiService可以让开发者更专注于业务
- 动态代理只是将功能代码从业务剥离,并解决了模板代码问题
- ReturnT、ResponseT引入适配器模式可以让结果更加灵活