Retrofit中的注解疑惑
阅读了两篇kotlin的教程还是对注解含义这部分有些疑惑,原教程如下:
你应该知道的HTTP基础知识
你真的会用Retrofit2吗?Retrofit2完全教程
用Retrofit的过程中,通常会用到注解去定义请求类型,如GET,POST. 还有一些标记FormUrlEncoded, MultiPart, 另外还有一些参数Header,Body,Field,Query,Url等。使用过程有时候摸不着头脑,不知道这些注解应该怎样配合使用,它们的原理到底是什么今天来总结一下.
通常interface上面的注解会解析成一个Request.
我们先从源码角度看看,从注解到Request到底是一个怎样过程
从注解到参数,这里分成两部分,一部分是方法注解,一部分是参数注解.
在ServiceMethod源码中可以看到下面的代码
private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof DELETE) {
parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if (annotation instanceof HEAD) {
parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
if (!Void.class.equals(responseType)) {
throw methodError("HEAD method must use Void as response type.");
}
} else if (annotation instanceof PATCH) {
parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
} else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof PUT) {
parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
} else if (annotation instanceof OPTIONS) {
parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError("@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
} else if (annotation instanceof Multipart) {
if (isFormEncoded) {
throw methodError("Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
if (isMultipart) {
throw methodError("Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
}
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
if (this.httpMethod != null) {
throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod, httpMethod);
}
this.httpMethod = httpMethod;
this.hasBody = hasBody;
if (value.isEmpty()) {
return;
}
// Get the relative URL path and existing query string, if present.
int question = value.indexOf('?');
if (question != -1 && question < value.length() - 1) {
// Ensure the query string does not have any named parameters.
String queryParams = value.substring(question + 1);
Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
if (queryParamMatcher.find()) {
throw methodError("URL query string \"%s\" must not have replace block. "
+ "For dynamic query parameters use @Query.", queryParams);
}
}
this.relativeUrl = value;
this.relativeUrlParamNames = parsePathParameters(value);
}
这里会对方法注解进行解析,并保存到几个成员变量当中,例如里面的
httpMethod
hasBody
relativeUrl
relativeUrlParamNames
在toRequest中会使用到这些参数转换成Request.
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
contentType, hasBody, isFormEncoded, isMultipart);
return requestBuilder.build();
}
我们再来看看Request的结构
public final class Request {
private final HttpUrl url;
private final String method;
private final Headers headers;
private final RequestBody body;
private final Object tag;
}
方法的注解解析后放在method里面,这里不在赘述。
参数通常有以下几种:
image.png
我们一个一个来看看它是如何解析的. 在parseParameterAnnotation函数中
if (annotation instanceof Url) {
return new ParameterHandler.RelativeUrl();
}
else if (annotation instanceof Path) {
Path path = (Path) annotation;
return new ParameterHandler.Path<>(name, converter, path.encoded());
}
else if (annotation instanceof Query) {
Query query = (Query) annotation;
return new ParameterHandler.Query<>(name, converter, encoded);
}
else if (annotation instanceof QueryMap) {
return new ParameterHandler.QueryMap<>(valueConverter, ((QueryMap)annotation).encoded());
}
else if (annotation instanceof Header) {
Header header = (Header) annotation;
return new ParameterHandler.Header<>(name, converter).iterable();
}
else if (annotation instanceof HeaderMap) {
Class<?> rawParameterType = Utils.getRawType(type);
return new ParameterHandler.HeaderMap<>(valueConverter);
}
else if (annotation instanceof Field) {
Field field = (Field) annotation;
String name = field.value();
return new ParameterHandler.Field<>(name, converter, encoded);
}
else if (annotation instanceof FieldMap) {
return new ParameterHandler.FieldMap<>(valueConverter, ((FieldMap) annotation).encoded());
}
else if (annotation instanceof Part) {
Part part = (Part) annotation;
return new ParameterHandler.Part<>(headers, converter);
}
else if (annotation instanceof PartMap) {
PartMap partMap = (PartMap) annotation;
return new ParameterHandler.PartMap<>(valueConverter, partMap.encoding());
}
else if (annotation instanceof Body) {
return new ParameterHandler.Body<>(converter);
}
上面代码经过省略,大概意思就是通过解析参数注解会实例化一个ParameterHandler中的一个类,具体看一下ParameterHandler是个什么类
abstract class ParameterHandler<T> {
abstract void apply(RequestBuilder builder, T value) throws IOException;
static final class Header<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
@Override void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) return; // Skip null values.
builder.addHeader(name, valueConverter.convert(value));
}
}
static final class Path<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
private final boolean encoded;
Path(String name, Converter<T, String> valueConverter, boolean encoded) {
this.name = checkNotNull(name, "name == null");
this.valueConverter = valueConverter;
this.encoded = encoded;
}
@Override void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) {
throw new IllegalArgumentException(
"Path parameter \"" + name + "\" value must not be null.");
}
builder.addPathParam(name, valueConverter.convert(value), encoded);
}
}
static final class QueryMap<T> extends ParameterHandler<Map<String, T>> {
private final Converter<T, String> valueConverter;
private final boolean encoded;
QueryMap(Converter<T, String> valueConverter, boolean encoded) {
this.valueConverter = valueConverter;
this.encoded = encoded;
}
@Override void apply(RequestBuilder builder, Map<String, T> value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Query map was null.");
}
for (Map.Entry<String, T> entry : value.entrySet()) {
String entryKey = entry.getKey();
if (entryKey == null) {
throw new IllegalArgumentException("Query map contained null key.");
}
T entryValue = entry.getValue();
if (entryValue == null) {
throw new IllegalArgumentException(
"Query map contained null value for key '" + entryKey + "'.");
}
builder.addQueryParam(entryKey, valueConverter.convert(entryValue), encoded);
}
}
}
//代码省略...
}
从上面可以看出来ParameterHandler只是一个抽象类,具体实现由其嵌套类实现.
Header解析过程
ServiceMethod.class对Header注解进行转换
else if (annotation instanceof Header) {
Header header = (Header) annotation;
return new ParameterHandler.Header<>(name, converter);
}
ParameterHandler.class apply在构造Request的时候会调用到
static final class Header<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
Header(String name, Converter<T, String> valueConverter) {
this.name = checkNotNull(name, "name == null");
this.valueConverter = valueConverter;
}
@Override void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) return; // Skip null values.
builder.addHeader(name, valueConverter.convert(value));
}
}
实质在RequestBuilder中去调用addHeader
void addHeader(String name, String value) {
if ("Content-Type".equalsIgnoreCase(name)) {
MediaType type = MediaType.parse(value);
if (type == null) {
throw new IllegalArgumentException("Malformed content type: " + value);
}
contentType = type;
} else {
requestBuilder.addHeader(name, value);
}
}
最终会在Request中添加这个header.
public Builder addHeader(String name, String value) {
headers.add(name, value);
return this;
}
总结:所以说header这个注解他是直接加载到了Request的Headers中的
Body
ServiceMethod.class对Body注解进行转换
else if (annotation instanceof Body) {
return new ParameterHandler.Body<>(converter);
}
ParameterHandler.class apply会设置这个body到RequestBuilder中
static final class Body<T> extends ParameterHandler<T> {
private final Converter<T, RequestBody> converter;
Body(Converter<T, RequestBody> converter) {
this.converter = converter;
}
@Override void apply(RequestBuilder builder, T value) {
try {
body = converter.convert(value);
} catch (IOException e) {
throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
}
builder.setBody(body);
}
}
RequestBuilder.class
void setBody(RequestBody body) {
this.body = body;
}
最终Request会去获取这个body
private Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}
总结:所以说如果你设置了body注解那就是在指定request中的body
Field
else if (annotation instanceof Field) {
return new ParameterHandler.Field<>(name, converter, encoded).iterable();
}
static final class Field<T> extends ParameterHandler<T> {
@Override void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) return; // Skip null values.
builder.addFormField(name, valueConverter.convert(value), encoded);
}
}
void addFormField(String name, String value, boolean encoded) {
if (encoded) {
formBuilder.addEncoded(name, value);
} else {
formBuilder.add(name, value);
}
}
在http的body中,有一种表单的数据结构叫做Form, 当指定Field注解其实就是在指定From的内容.
public final class FormBody extends RequestBody {
public static final class Builder {
private final List<String> names = new ArrayList<>();
private final List<String> values = new ArrayList<>();
public Builder add(String name, String value) {
names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, false, false, true, true));
values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, false, false, true, true));
return this;
}
}
在RequestBuilder.class中如果formBuilder != null 就会去创建FormBody.
Request build() {
RequestBody body = this.body;
if (body == null) {
// Try to pull from one of the builders.
if (formBuilder != null) {
body = formBuilder.build();
} else if (multipartBuilder != null) {
body = multipartBuilder.build();
} else if (hasBody) {
// Body is absent, make an empty body.
body = RequestBody.create(null, new byte[0]);
}
}
那formBuilder是在哪里创建的呢?在RequestBuilder构造函数中isFormEncoded为true就会创建
RequestBuilder(String method, HttpUrl baseUrl, String relativeUrl, Headers headers,
MediaType contentType, boolean hasBody, boolean isFormEncoded, boolean isMultipart) {
if (isFormEncoded) {
// Will be set to 'body' in 'build'.
formBuilder = new FormBody.Builder();
}
}
而isFormEncoded的值是由FormUrlEncoded注解决定的.
if (annotation instanceof FormUrlEncoded) {
if (isMultipart) {
throw methodError("Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
总结:Field注解主要用于指定表单数据结构,前提必须使用@FormUrlEncoded注解表示创建表单的body.
Part
if (annotation instanceof Part){
Part part = (Part) annotation;
return new ParameterHandler.Part<>(headers, converter).iterable();
}
static final class Part<T> extends ParameterHandler<T> {
@Override void apply(RequestBuilder builder, T value) {
if (value == null) return; // Skip null values.
RequestBody body;
try {
body = converter.convert(value);
} catch (IOException e) {
throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
}
builder.addPart(headers, body);
}
}
void addPart(Headers headers, RequestBody body) {
multipartBuilder.addPart(headers, body);
}
添加到multipartBuilder里面
Request build() {
RequestBody body = this.body;
if (body == null) {
// Try to pull from one of the builders.
if (formBuilder != null) {
body = formBuilder.build();
} else if (multipartBuilder != null) {
body = multipartBuilder.build();
} else if (hasBody) {
// Body is absent, make an empty body.
body = RequestBody.create(null, new byte[0]);
}
}
return requestBuilder
.url(url)
.method(method, body)
.build();
}
最终multipartBuilder != null的情况下会去multipartBuilder.build()去构建这个body。是否创建还取决于是否有@Multipart注解. 所以Part必须和@Multipart注解配合使用,这个注解通常用来上传文件.
举个栗子:
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "怪盗kidou");
RequestBody age = RequestBody.create(textType, "24");
RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");
// 演示 @Multipart 和 @Part
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
MultipartBody是个什么结构,从源码角度来看
public final class MultipartBody extends RequestBody {
private final List<Part> parts;
}
MultipartBody实质也是一个RequestBody,但是它里面维护了一个List<Part>
public static final class Part {
private final Headers headers;
private final RequestBody body;
}
这意味者MultipartBody里面还维护了这样的结构
image.png
Query
ServiceMethod.class中对Query注解进行解析
else if (annotation instanceof Query) {
Query query = (Query) annotation;
return new ParameterHandler.Query<>(name, converter, encoded);
}
ParameterHandler.class中添加addQueryParam
static final class Query<T> extends ParameterHandler<T> {
@Override void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) return; // Skip null values.
builder.addQueryParam(name, valueConverter.convert(value), encoded);
}
}
在RequestBuilder中去添加
void addQueryParam(String name, String value, boolean encoded) {
if (relativeUrl != null) {
// Do a one-time combination of the built relative URL and the base URL.
urlBuilder = baseUrl.newBuilder(relativeUrl);
if (urlBuilder == null) {
throw new IllegalArgumentException(
"Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
}
relativeUrl = null;
}
放在了urlBuilder中.
if (encoded) {
urlBuilder.addEncodedQueryParameter(name, value);
} else {
urlBuilder.addQueryParameter(name, value);
}
}
从源码角度可以看出Query注解参数是直接加到了url后面.
总结:可以看出来Query注解还是比较容易理解的,直接是加在了url的后面.
Url
这个是用来通过注解替换baseUrl的
ServiceMethod.class中对Url注解进行解析
if (annotation instanceof Url) {
return new ParameterHandler.RelativeUrl();
}
ParameterHandler.class中setRelativeUrl
static final class RelativeUrl extends ParameterHandler<Object> {
@Override void apply(RequestBuilder builder, Object value) {
builder.setRelativeUrl(value);
}
}
void setRelativeUrl(Object relativeUrl) {
if (relativeUrl == null) throw new NullPointerException("@Url parameter is null.");
this.relativeUrl = relativeUrl.toString();
}
最后在RequestBuilder的build中替relativeUrl
Request build() {
url = baseUrl.resolve(relativeUrl);
}
在Request的build中会替换到之前的baseUrl.