Retrofit源码解析

之前花了一段时间整理过一篇文章OKHttp源码解析。所以今天打算把一个包装工具Retrofit做一下源码解析。

Retrofit和Java领域的ORM概念类似,ORM把结构化数据转换为Java对象,而Retrofit把REST API返回的数据转化为Java对象方便操作。同时还封装了网络代码的调用。这个网络代码默认采用了OKHttp的方式。

Retrofit使用

这一节主要使用源码内部的一个实例来展示Retrofit的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class GitHubClient {
private static final String API_URL = "https://api.github.com";

static class Contributor {
String login;
int contributions;
}

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

public static void main(String... args) {
// Create a very simple REST adapter which points the GitHub API endpoint.
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(API_URL)
.build();

// Create an instance of our GitHub API interface.
GitHub github = restAdapter.create(GitHub.class);

// Fetch and print a list of the contributors to this library.
List<Contributor> contributors = github.contributors("square", "retrofit");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}

  • 定义一个REST API接口。该接口定义了一个函数listRepos , 该函数会通过HTTP GET请求去访问服务器的/users/{user}/repos路径并把返回的结果封装为List Java对象返回。其中URL路径中的{user}的值为listRepos函数中的参数user的取值。然后通过RestAdapter类来生成一个GitHubService接口的实现;
  • 获取接口的实现,调用接口函数来和服务器交互;

RestAdapter.Builder 构建器模式

Builder模式主要出发点是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
使用场景:经常在构造器中装配的域非常多、同时不同场景下需要初始化的域(或者传入的域)不一样的时候。这样的好处就是按需构造,非常灵活。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* Build a new {@link RestAdapter}.
* <p>
* Calling {@link #setEndpoint} is required before calling {@link #build()}. All other methods
* are optional.
*/
public static class Builder {
private Endpoint endpoint;
private OkHttpClient client;
private Executor callbackExecutor;
private RequestInterceptor requestInterceptor;
private Converter converter;
private ErrorHandler errorHandler;

// ....省略...

/** Create the {@link RestAdapter} instances. */
public RestAdapter build() {
if (endpoint == null) {
throw new IllegalArgumentException("Endpoint may not be null.");
}
ensureSaneDefaults();
return new RestAdapter(endpoint, client, callbackExecutor, requestInterceptor, converter,
errorHandler);
}

private void ensureSaneDefaults() {
if (converter == null) {
converter = Platform.get().defaultConverter();
}
if (client == null) {
client = Platform.get().defaultClient();
}
if (callbackExecutor == null) {
callbackExecutor = Platform.get().defaultCallbackExecutor();
}
if (errorHandler == null) {
errorHandler = ErrorHandler.DEFAULT;
}
if (requestInterceptor == null) {
requestInterceptor = RequestInterceptor.NONE;
}
}
}

在RestAdapter需要指定url根地址、采用的网络客户端、回调线程池、请求拦截器、返回数据格式器和错误处理。这些参数在Builder中得到了接管,不过值得注意的是RestAdapter不应该持有Builder(之前曾经看到过一些开发同学这样干过)。参数在在builder中都创建了默认值(默认自适应平台,默认返回数据JSON格式化,默认Error处理方式以及请求拦截器),默认值是提高代码健壮性的一中方式,这是一个非常好的习惯。留给使用的只需要指定endpoint就可以工作了。

RestAdapter.create 代理模式

很多同学在开发中或多或少是遇到过代理,而实际使用我想肯定不多。感觉也不是那么好用。在上面的实例中,RestAdapter.create很好的展现出了java中对代理的支持与实现应用。

1
2
3
4
5
public <T> T create(Class<T> service) {
Utils.validateServiceClass(service);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new RestHandler(getMethodInfoCache(service)));
}

Proxy.newProxyInstance使用这里不做介绍,重点说说实现了InvocationHandler接口的RestHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  private class RestHandler implements InvocationHandler {
private final Map<Method, MethodInfo> methodDetailsCache;

RestHandler(Map<Method, MethodInfo> methodDetailsCache) {
this.methodDetailsCache = methodDetailsCache;
}

@SuppressWarnings("unchecked") //
@Override public Object invoke(Object proxy, Method method, final Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}

MethodInfo methodInfo = getMethodInfo(methodDetailsCache, method);
Request request = createRequest(methodInfo, args);
switch (methodInfo.executionType) {
case SYNC:
return invokeSync(methodInfo, request);
case ASYNC:
invokeAsync(methodInfo, request, (Callback) args[args.length - 1]);
return null; // Async has void return type.
case RX:
return invokeRx(methodInfo, request);
default:
throw new IllegalStateException("Unknown response type: " + methodInfo.executionType);
}
}
}

当调用起github.contributors(“square”, “retrofit”)这个方法的时候,会触发RestHandler的拦截。下面一步一步来看看在拦截的地方做了什么:
在第一步如果是构造器方法则返回应该不难理解,这里主要说方法的拆分与缓存(getMethodInfo(methodDetailsCache, method)):
1、根据当前方法获取缓存中解析的方法信息,如果有就不用再去解析方法,反之重新创建方法信息

1
2
3
4
5
6
7
8
9
10
static MethodInfo getMethodInfo(Map<Method, MethodInfo> cache, Method method) {
synchronized (cache) {
MethodInfo methodInfo = cache.get(method);
if (methodInfo == null) {
methodInfo = new MethodInfo(method);
cache.put(method, methodInfo);
}
return methodInfo;
}
}

2、根据当前方法解析方法信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
final class MethodInfo {

enum ExecutionType {
ASYNC,
RX,
SYNC
}
enum RequestType {
/** No content-specific logic required. */
SIMPLE,
/** Multi-part request body. */
MULTIPART,
/** Form URL-encoded request body. */
FORM_URL_ENCODED
}

MethodInfo(Method method) {
this.method = method;
executionType = parseResponseType();

parseMethodAnnotations();
parseParameters();
}

/** Loads {@link #responseObjectType}. */
private ExecutionType parseResponseType() {
// Synchronous methods have a non-void return type.
// Observable methods have a return type of Observable.
Type returnType = method.getGenericReturnType();

// Asynchronous methods should have a Callback type as the last argument.
Type lastArgType = null;
Class<?> lastArgClass = null;
Type[] parameterTypes = method.getGenericParameterTypes();
if (parameterTypes.length > 0) {
Type typeToCheck = parameterTypes[parameterTypes.length - 1];
lastArgType = typeToCheck;
if (typeToCheck instanceof ParameterizedType) {
typeToCheck = ((ParameterizedType) typeToCheck).getRawType();
}
if (typeToCheck instanceof Class) {
lastArgClass = (Class<?>) typeToCheck;
}
}

boolean hasReturnType = returnType != void.class;
boolean hasCallback = lastArgClass != null && Callback.class.isAssignableFrom(lastArgClass);

// Check for invalid configurations.
if (hasReturnType && hasCallback) {
throw methodError("Must have return type or Callback as last argument, not both.");
}
if (!hasReturnType && !hasCallback) {
throw methodError("Must have either a return type or Callback as last argument.");
}

if (hasReturnType) {
if (Platform.HAS_RX_JAVA) {
Class rawReturnType = Types.getRawType(returnType);
if (RxSupport.isObservable(rawReturnType)) {
returnType = RxSupport.getObservableType(returnType, rawReturnType);
responseObjectType = getParameterUpperBound((ParameterizedType) returnType);
return ExecutionType.RX;
}
}
responseObjectType = returnType;
return ExecutionType.SYNC;
}

lastArgType = Types.getSupertype(lastArgType, Types.getRawType(lastArgType), Callback.class);
if (lastArgType instanceof ParameterizedType) {
responseObjectType = getParameterUpperBound((ParameterizedType) lastArgType);
return ExecutionType.ASYNC;
}

throw methodError("Last parameter must be of type Callback<X> or Callback<? super X>.");
}

/** Loads {@link #requestMethod} and {@link #requestType}. */
private void parseMethodAnnotations() {
for (Annotation methodAnnotation : method.getAnnotations()) {
Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
if (annotationType == DELETE.class) {
parseHttpMethodAndPath("DELETE", ((DELETE) methodAnnotation).value(), false);
} else if (annotationType == GET.class) {
parseHttpMethodAndPath("GET", ((GET) methodAnnotation).value(), false);
} else if (annotationType == HEAD.class) {
parseHttpMethodAndPath("HEAD", ((HEAD) methodAnnotation).value(), false);
} else if (annotationType == PATCH.class) {
parseHttpMethodAndPath("PATCH", ((PATCH) methodAnnotation).value(), true);
} else if (annotationType == POST.class) {
parseHttpMethodAndPath("POST", ((POST) methodAnnotation).value(), true);
} else if (annotationType == PUT.class) {
parseHttpMethodAndPath("PUT", ((PUT) methodAnnotation).value(), true);
} else if (annotationType == HTTP.class) {
HTTP http = (HTTP) methodAnnotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotationType == Headers.class) {
String[] headersToParse = ((Headers) methodAnnotation).value();
if (headersToParse.length == 0) {
throw methodError("@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
} else if (annotationType == Multipart.class) {
if (requestType != RequestType.SIMPLE) {
throw methodError("Only one encoding annotation is allowed.");
}
throw new UnsupportedOperationException("Multipart shall return!");
//requestType = RequestType.MULTIPART;
} else if (annotationType == FormUrlEncoded.class) {
if (requestType != RequestType.SIMPLE) {
throw methodError("Only one encoding annotation is allowed.");
}
throw new UnsupportedOperationException("Form URL encoding shall return!");
//requestType = RequestType.FORM_URL_ENCODED;
} else if (annotationType == Streaming.class) {
if (responseObjectType != Response.class) {
throw methodError(
"Only methods having %s as data type are allowed to have @%s annotation.",
Response.class.getSimpleName(), Streaming.class.getSimpleName());
}
isStreaming = true;
}
}

if (requestMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!requestHasBody) {
if (requestType == RequestType.MULTIPART) {
throw methodError(
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (requestType == RequestType.FORM_URL_ENCODED) {
throw methodError("FormUrlEncoded can only be specified on HTTP methods with request body "
+ "(e.g., @POST).");
}
}
}

/**
* Loads {@link #requestParamAnnotations}. Must be called after {@link #parseMethodAnnotations()}.
*/
private void parseParameters() {
Type[] methodParameterTypes = method.getGenericParameterTypes();

Annotation[][] methodParameterAnnotationArrays = method.getParameterAnnotations();
int count = methodParameterAnnotationArrays.length;
if (executionType == ExecutionType.ASYNC) {
count -= 1; // Callback is last argument when not a synchronous method.
}

Annotation[] requestParamAnnotations = new Annotation[count];

boolean gotField = false;
boolean gotPart = false;
boolean gotBody = false;

for (int i = 0; i < count; i++) {
Type methodParameterType = methodParameterTypes[i];
Annotation[] methodParameterAnnotations = methodParameterAnnotationArrays[i];
if (methodParameterAnnotations != null) {
for (Annotation methodParameterAnnotation : methodParameterAnnotations) {
Class<? extends Annotation> methodAnnotationType =
methodParameterAnnotation.annotationType();

if (methodAnnotationType == Path.class) {
String name = ((Path) methodParameterAnnotation).value();
validatePathName(i, name);
} else if (methodAnnotationType == Query.class) {
// Nothing to do.
} else if (methodAnnotationType == QueryMap.class) {
if (!Map.class.isAssignableFrom(Types.getRawType(methodParameterType))) {
throw parameterError(i, "@QueryMap parameter type must be Map.");
}
} else if (methodAnnotationType == Header.class) {
// Nothing to do.
} else if (methodAnnotationType == Field.class) {
if (requestType != RequestType.FORM_URL_ENCODED) {
throw parameterError(i, "@Field parameters can only be used with form encoding.");
}

gotField = true;
} else if (methodAnnotationType == FieldMap.class) {
if (requestType != RequestType.FORM_URL_ENCODED) {
throw parameterError(i, "@FieldMap parameters can only be used with form encoding.");
}
if (!Map.class.isAssignableFrom(Types.getRawType(methodParameterType))) {
throw parameterError(i, "@FieldMap parameter type must be Map.");
}

gotField = true;
} else if (methodAnnotationType == Part.class) {
if (requestType != RequestType.MULTIPART) {
throw parameterError(i, "@Part parameters can only be used with multipart encoding.");
}

gotPart = true;
} else if (methodAnnotationType == PartMap.class) {
if (requestType != RequestType.MULTIPART) {
throw parameterError(i,
"@PartMap parameters can only be used with multipart encoding.");
}
if (!Map.class.isAssignableFrom(Types.getRawType(methodParameterType))) {
throw parameterError(i, "@PartMap parameter type must be Map.");
}

gotPart = true;
} else if (methodAnnotationType == Body.class) {
if (requestType != RequestType.SIMPLE) {
throw parameterError(i,
"@Body parameters cannot be used with form or multi-part encoding.");
}
if (gotBody) {
throw methodError("Multiple @Body method annotations found.");
}

requestObjectType = methodParameterType;
gotBody = true;
} else {
// This is a non-Retrofit annotation. Skip to the next one.
continue;
}

if (requestParamAnnotations[i] != null) {
throw parameterError(i,
"Multiple Retrofit annotations found, only one allowed: @%s, @%s.",
requestParamAnnotations[i].annotationType().getSimpleName(),
methodAnnotationType.getSimpleName());
}
requestParamAnnotations[i] = methodParameterAnnotation;
}
}

if (requestParamAnnotations[i] == null) {
throw parameterError(i, "No Retrofit annotation found.");
}
}

if (requestType == RequestType.SIMPLE && !requestHasBody && gotBody) {
throw methodError("Non-body HTTP method cannot contain @Body or @TypedOutput.");
}
if (requestType == RequestType.FORM_URL_ENCODED && !gotField) {
throw methodError("Form-encoded method must contain at least one @Field.");
}
if (requestType == RequestType.MULTIPART && !gotPart) {
throw methodError("Multipart method must contain at least one @Part.");
}

this.requestParamAnnotations = requestParamAnnotations;
}
}

这一段代码有点多,本打算只截取最重要的地方,不过后来发现还是全部展示出来最有好的说服力。
在最开始实例中定义了一个API接口,采用了Annotation注解的方式定义了每一个网络请求的方式(GET/POST,相对路径,在路径中的请求参数,方法参数中的请求参数)。

  1. 解析执行类型,在这里代码采用了检测最后一个参数是否是Callback类型做判断,如果最后一个是Callback类型参数,那么采用异步的方式,反之采用同步。另外在JAVA平台的时候还会根据返回类型来判断是否符合RX方式。
  2. 解析Annotation注解(重点解析GET/POST,相对路径,在路径中的请求参数)。标签非常的多(GET,POST,PUT,DELETE和HEAD),主要是指定每一个作用域的意图。更值得一提的是在相对路径中可以采用“xxx?a=1&b=2”的方式带入参数,也可以使用@Path,@Query,@Body,@Field来表示。
  3. 在请求参数的解析方法中,根据这些不同的标注来返回当前的请求方式是普通请求、multi-part还是form形式。

在最后的invoke调用中只需要根据当前方法拿到解析出来的方法信息执行对应的网络请求即可。

1.同步直接返回数据
2.异步这加入异步调用队列,采用Callback返回
3.RX方式(省略…)

专门再看一下Retrofit源码,主要是个人觉得采用了注解+反射代理的方式可以非常灵活的将复杂的业务逻辑进行拆分解耦。同时从代码结构上来看也会非常的清晰。这都是值得开发者学习的地方。