之前花了一段时间整理过一篇文章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
33
|
public class GitHubClient {
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 +
")"
);
}
}
interface GitHub {
@GET(
"/repos/{owner}/{repo}/contributors"
)
List<Contributor> contributors(@Path(
"owner"
)
String owner, @Path(
"repo"
)
String repo);
}
static class Contributor {
String login;
int 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
45
46
47
48
49
|
/**
* 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
|
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
31
32
33
34
35
36
37
38
|
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
|
final class MethodInfo {
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
;
|
接上(代码太长了分成两段)
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
|
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;
}
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;
}
}
|
这一段代码有点多,本打算只截取最重要的地方,不过后来发现还是全部展示出来最有好的说服力。
在最开始实例中定义了一个API接口,采用了Annotation注解的方式定义了每一个网络请求的方式(GET/POST,相对路径,在路径中的请求参数,方法参数中的请求参数)。
解析执行类型,在这里代码采用了检测最后一个参数是否是Callback类型做判断,如果最后一个是Callback类型参数,那么采用异步的方式,反之采用同步。另外在JAVA平台的时候还会根据返回类型来判断是否符合RX方式。
解析Annotation注解(重点解析GET/POST,相对路径,在路径中的请求参数)。标签非常的多 (GET,POST,PUT,DELETE和HEAD),主要是指定每一个作用域的意图。更值得一提的是在相对路径中可以采用“xxx?a=1& b=2”的方式带入参数,也可以使用@Path,@Query,@Body,@Field来表示。
在请求参数的解析方法中,根据这些不同的标注来返回当前的请求方式是普通请求、multi-part还是form形式。
在最后的invoke调用中只需要根据当前方法拿到解析出来的方法信息执行对应的网络请求即可。
1.同步直接返回数据
2.异步这加入异步调用队列,采用Callback返回
3.RX方式(省略…)