Retrofit中的注解疑惑

2018-05-23  本文已影响51人  e小e

阅读了两篇kotlin的教程还是对注解含义这部分有些疑惑,原教程如下:
你应该知道的HTTP基础知识
你真的会用Retrofit2吗?Retrofit2完全教程
用Retrofit的过程中,通常会用到注解去定义请求类型,如GET,POST. 还有一些标记FormUrlEncoded, MultiPart, 另外还有一些参数Header,Body,Field,Query,Url等。使用过程有时候摸不着头脑,不知道这些注解应该怎样配合使用,它们的原理到底是什么今天来总结一下.
通常interface上面的注解会解析成一个Request.

image.png
我们先从源码角度看看,从注解到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.

上一篇下一篇

猜你喜欢

热点阅读