美文网首页
Retrofit中的注解疑惑

Retrofit中的注解疑惑

作者: e小e | 来源:发表于2018-05-23 16:28 被阅读51次

    阅读了两篇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.

    相关文章

      网友评论

          本文标题:Retrofit中的注解疑惑

          本文链接:https://www.haomeiwen.com/subject/znzhjftx.html