Retrofit解析8之核心解析——ServiceMethod及注解1

整體Retrofit內(nèi)容如下:

上篇文章已經(jīng)介紹了Retrofit里面的大多數(shù)類藏澳,今天就重點介紹ServiceMethod,本片文章主要內(nèi)容如下:

  • 1耀找、ParameterHandler類
  • 2翔悠、ServiceMethod類
  • 3、Retrofit類

為了方便后面學習ServiceMethod涯呻,我們先來看下ParameterHandler類

一凉驻、ParameterHandler類

(一)腻要、ParameterHandler類是什么复罐?

從字面意思我們猜到 他是參數(shù)處理類。具體是不是那雄家?用源碼說話效诅,看源碼

abstract class ParameterHandler<T> {
  abstract void apply(RequestBuilder builder, T value) throws IOException;
  final ParameterHandler<Iterable<T>> iterable() {
    return new ParameterHandler<Iterable<T>>() {
      @Override void apply(RequestBuilder builder, Iterable<T> values) throws IOException {
        if (values == null) return; // Skip null values.
        for (T value : values) {
          ParameterHandler.this.apply(builder, value);
        }
      }
    };
  }
  final ParameterHandler<Object> array() {
    return new ParameterHandler<Object>() {
      @Override void apply(RequestBuilder builder, @Nullable Object values) throws IOException {
        if (values == null) return; // Skip null values.

        for (int i = 0, size = Array.getLength(values); i < size; i++) {
          //noinspection unchecked
          ParameterHandler.this.apply(builder, (T) Array.get(values, i));
        }
      }
    };
  }
  //由于篇幅的限制,我沒有粘貼靜態(tài)內(nèi)部類
}

通過簡單閱讀源代碼,我們得知:

  • ParameterHandler 是一個抽象類
  • 一共有3個方法乱投,其中一個是抽象方法
  • apply(RequestBuilder builder, T value)方法是抽象的咽笼,需要子類去實現(xiàn)
  • iterable()方法返回的是new的一個ParameterHandler,不過的他的泛型是Iterable<T>戚炫,所以它的抽象方法的具體實現(xiàn)是遍歷這個迭代器Iterable剑刑,調(diào)用這個對象的apply()方法,注意這個方法是final双肤。
  • array()方法 返回的是一個new的一個ParameterHandler施掏,它的泛型是Object,而他的抽象方法是通過遍歷數(shù)組的方式來分別調(diào)用對應的apply()方法茅糜,這個方法也是final的

這里說下iterable()方法和array()方法七芭,說明ParameterHandler是支持兩種數(shù)據(jù)類型的處理方式。一個是對應迭代器(iterable)的類型;一個是對應的是數(shù)組類型的蔑赘。所以我們繼續(xù)往下看

(二)狸驳、靜態(tài)內(nèi)部類RelativeUrl

看下源碼:

  static final class RelativeUrl extends ParameterHandler<Object> {
    @Override void apply(RequestBuilder builder, Object value) {
      builder.setRelativeUrl(value);
    }
  }
  • 首先,這個類是final的
  • 其次缩赛,貌似很簡單耙箍,RelativeUrl類內(nèi)部就是調(diào)用RequestBuilder的setRelativeUrl方法來設置RequestBuilder的relativeUrl的值

(三)、靜態(tài)內(nèi)部類Header

看下源碼:

  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));
    }
  }
  • 首先這個類是final的
  • 其次這個類有兩個final的變量峦筒,在構造的時候賦值
  • apply抽象方法也是調(diào)用RequestBuilder的addHeader方法給RequestBuilder的header賦值
  • apply里面有調(diào)用Converter的convert的方法把value進行轉化究西,其實是序列化。

(四)物喷、靜態(tài)內(nèi)部類Path

看下源碼:

    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);
    }
  • 首先這個類是final的
  • 其次這個類有三個final的變量卤材,在構造的時候賦值
  • apply抽象方法也是調(diào)用RequestBuilder的addPathParam方法進行設置
  • apply里面有調(diào)用Converter的convert的方法把value進行轉化,其實是序列化峦失。

(五)扇丛、靜態(tài)內(nèi)部類Query

看下源碼:

  static final class Query<T> extends ParameterHandler<T> {
    private final String name;
    private final Converter<T, String> valueConverter;
    private final boolean encoded;

    Query(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) return; // Skip null values.
      builder.addQueryParam(name, valueConverter.convert(value), encoded);
    }
  }
  • 首先這個類是final的
  • 其次這個類有三個final的變量,在構造的時候賦值
  • apply抽象方法也是調(diào)用RequestBuilder的addQueryParam方法進行設置
  • apply里面有調(diào)用Converter的convert的方法把value進行轉化尉辑,其實是序列化帆精。

(六)、靜態(tài)內(nèi)部類QueryName

看下源碼:

  static final class QueryName<T> extends ParameterHandler<T> {
    private final Converter<T, String> nameConverter;
    private final boolean encoded;
    QueryName(Converter<T, String> nameConverter, boolean encoded) {
      this.nameConverter = nameConverter;
      this.encoded = encoded;
    }
    @Override void apply(RequestBuilder builder, T value) throws IOException {
      if (value == null) return; // Skip null values.
      builder.addQueryParam(nameConverter.convert(value), null, encoded);
    }
  }
  • 首先這個類是final的
  • 其次這個類有二個final的變量隧魄,在構造的時候賦值
  • apply抽象方法也是調(diào)用RequestBuilder的addQueryParam方法進行設置
  • apply里面有調(diào)用Converter的convert的方法把value進行轉化卓练,其實是序列化。

(七)购啄、靜態(tài)內(nèi)部類QueryMap

看下源碼:

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);
      }
    }
  }
  • 首先這個類是final的
  • 其次這個類有二個final的變量襟企,在構造的時候賦值
  • apply抽象方法內(nèi)部首先遍歷Map,獲取對應的key 和value,然后調(diào)用RequestBuilder的addQueryParam方法進行設置
  • apply里面有調(diào)用Converter的convert的方法把value進行轉化狮含,其實是序列化顽悼。

(八)曼振、靜態(tài)內(nèi)部類HeaderMap

看下源碼:

 static final class HeaderMap<T> extends ParameterHandler<Map<String, T>> {
    private final Converter<T, String> valueConverter;

    HeaderMap(Converter<T, String> valueConverter) {
      this.valueConverter = valueConverter;
    }

    @Override void apply(RequestBuilder builder, Map<String, T> value) throws IOException {
      if (value == null) {
        throw new IllegalArgumentException("Header map was null.");
      }

      for (Map.Entry<String, T> entry : value.entrySet()) {
        String headerName = entry.getKey();
        if (headerName == null) {
          throw new IllegalArgumentException("Header map contained null key.");
        }
        T headerValue = entry.getValue();
        if (headerValue == null) {
          throw new IllegalArgumentException(
              "Header map contained null value for key '" + headerName + "'.");
        }
        builder.addHeader(headerName, valueConverter.convert(headerValue));
      }
    }
  }
  • 首先這個類是final的
  • 其次這個類有一個final的變量,在構造的時候賦值
  • apply抽象方法內(nèi)部首先遍歷Map,獲取對應的key 和value蔚龙,然后調(diào)用RequestBuilder的addHeader方法進行設置
  • apply里面有調(diào)用Converter的convert的方法把value進行轉化冰评,其實是序列化。

(九)木羹、靜態(tài)內(nèi)部類Field

看下源碼:

static final class Field<T> extends ParameterHandler<T> {
    private final String name;
    private final Converter<T, String> valueConverter;
    private final boolean encoded;

    Field(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) return; // Skip null values.
      builder.addFormField(name, valueConverter.convert(value), encoded);
    }
  }
  • 首先這個類是final的
  • 其次這個類有三個final的變量甲雅,在構造的時候賦值
  • apply抽象方法內(nèi)部調(diào)用RequestBuilder的addFormField方法進行設置
  • apply里面有調(diào)用Converter的convert的方法把value進行轉化,其實是序列化坑填。

(十)务荆、靜態(tài)內(nèi)部類FieldMap

看下源碼:

static final class FieldMap<T> extends ParameterHandler<Map<String, T>> {
    private final Converter<T, String> valueConverter;
    private final boolean encoded;

    FieldMap(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("Field map was null.");
      }

      for (Map.Entry<String, T> entry : value.entrySet()) {
        String entryKey = entry.getKey();
        if (entryKey == null) {
          throw new IllegalArgumentException("Field map contained null key.");
        }
        T entryValue = entry.getValue();
        if (entryValue == null) {
          throw new IllegalArgumentException(
              "Field map contained null value for key '" + entryKey + "'.");
        }
        builder.addFormField(entryKey, valueConverter.convert(entryValue), encoded);
      }
    }
  }
  • 首先這個類是final的
  • 其次這個類有二個final的變量越驻,在構造的時候賦值
  • apply抽象方法內(nèi)部首先遍歷Map,獲取對應的key 和value紧武,然后調(diào)用RequestBuilder的addFormField方法進行設置
  • apply里面有調(diào)用Converter的convert的方法把value進行轉化,其實是序列化耍目。

(十一)蚪黑、靜態(tài)內(nèi)部類Part

看下源碼:

static final class Part<T> extends ParameterHandler<T> {
    private final Headers headers;
    private final Converter<T, RequestBody> converter;

    Part(Headers headers, Converter<T, RequestBody> converter) {
      this.headers = headers;
      this.converter = converter;
    }

    @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);
    }
  }
  • 首先這個類是final的
  • 其次這個類有二個final的變量盅惜,在構造的時候賦值
  • apply抽象方法內(nèi)部首先通過convert的方法把value進行轉化(其實是序列化),然后調(diào)用RequestBuilder的addPart方法進行設置忌穿。

(十二)抒寂、靜態(tài)內(nèi)部類RawPart

看下源碼:

static final RawPart INSTANCE = new RawPart();

    private RawPart() {
    }

    @Override void apply(RequestBuilder builder, MultipartBody.Part value) throws IOException {
      if (value != null) { // Skip null values.
        builder.addPart(value);
      }
    }
  • 首先這個類是final的
  • 其次這個類構造函數(shù)是私有的,并且定義了一個靜態(tài)的常量對象掠剑,所以這個是類的對象是單例模式屈芜。
  • apply抽象方法調(diào)用RequestBuilder的addPart方法進行設置。

(十三)朴译、靜態(tài)內(nèi)部類PartMap

看下源碼:

  static final class PartMap<T> extends ParameterHandler<Map<String, T>> {
    private final Converter<T, RequestBody> valueConverter;
    private final String transferEncoding;

    PartMap(Converter<T, RequestBody> valueConverter, String transferEncoding) {
      this.valueConverter = valueConverter;
      this.transferEncoding = transferEncoding;
    }

    @Override void apply(RequestBuilder builder, Map<String, T> value) throws IOException {
      if (value == null) {
        throw new IllegalArgumentException("Part map was null.");
      }

      for (Map.Entry<String, T> entry : value.entrySet()) {
        String entryKey = entry.getKey();
        if (entryKey == null) {
          throw new IllegalArgumentException("Part map contained null key.");
        }
        T entryValue = entry.getValue();
        if (entryValue == null) {
          throw new IllegalArgumentException(
              "Part map contained null value for key '" + entryKey + "'.");
        }

        Headers headers = Headers.of(
            "Content-Disposition", "form-data; name=\"" + entryKey + "\"",
            "Content-Transfer-Encoding", transferEncoding);

        builder.addPart(headers, valueConverter.convert(entryValue));
      }
    }
  }
  • 首先這個類是final的
  • 其次這個類有二個final的變量井佑,在構造的時候賦值
  • apply抽象方法內(nèi)部首先遍歷Map,獲取對應的key 和value,然后調(diào)用okhttp3的Headers.of()方法添加headers眠寿,最后調(diào)用RequestBuilder的addPart方法進行設置
  • apply里面有調(diào)用Converter的convert的方法把value進行轉化躬翁,其實是序列化。

(十四)盯拱、靜態(tài)內(nèi)部類Body

看下源碼:

   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) {
      if (value == null) {
        throw new IllegalArgumentException("Body parameter value must not be null.");
      }
      RequestBody body;
      try {
        body = converter.convert(value);
      } catch (IOException e) {
        throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
      }
      builder.setBody(body);
    }
  }
  • 首先這個類是final的
  • 其次這個類有一個final的變量盒发,在構造的時候賦值
  • apply抽象方法內(nèi)部首先調(diào)用Converter的convert的方法把value進行轉化,其實就是序列化狡逢。然后調(diào)用RequestBuilder的setBody方法進行設置

(十五)宁舰、總結

本來這篇內(nèi)容應該放到上一篇文章中,但是由于受到篇幅限制的原由奢浑,所以放到這里了蛮艰,看完ParameterHandler類的源碼在回想下ParameterHandler的源碼,大家有沒有想到一些東西?

其實Retrofit團隊是這樣分工的殷费,RequestBuilder僅僅是一個包裝類印荔,但是具體的賦值操作等其實是通過ParameterHandler對應的靜態(tài)內(nèi)部類來實現(xiàn)的,這樣實現(xiàn)了包裝和操作分離详羡,實現(xiàn)了解耦仍律。多么"優(yōu)雅"的設計。

二实柠、ServiceMethod類

(一)水泉、ServiceMethod類是什么?

首先用類注解解釋下ServiceMethod是什么窒盐?

/** Adapts an invocation of an interface method into an HTTP call. */

我翻譯一下:

將一個接口的方方法的調(diào)用適配成一個HTTP的Call草则。

我的理解是:

ServiceMethod是一個負責轉化(適配)的類,負責把一個接口的抽象方法的執(zhí)行過程的結果轉化(適配)成一個網(wǎng)絡請求(HTTP call)蟹漓。

ok 那我們來看下他的源碼

(二)炕横、ServiceMethod類源碼

由于這個類略大,一直在糾結要不要把全部代碼粘貼上葡粒,但是考慮到大家的情況份殿,還是粘貼了,不過省略了Buidler內(nèi)部靜態(tài)類的代碼嗽交,希望大家理解卿嘲。

1、ServiceMethod源碼:
/** Adapts an invocation of an interface method into an HTTP call. */
final class ServiceMethod<R, T> {
  // Upper and lower characters, digits, underscores, and hyphens, starting with a character.
  static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
  static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
  static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);

  final okhttp3.Call.Factory callFactory;
  final CallAdapter<R, T> callAdapter;

  private final HttpUrl baseUrl;
  private final Converter<ResponseBody, R> responseConverter;
  private final String httpMethod;
  private final String relativeUrl;
  private final Headers headers;
  private final MediaType contentType;
  private final boolean hasBody;
  private final boolean isFormEncoded;
  private final boolean isMultipart;
  private final ParameterHandler<?>[] parameterHandlers;

  ServiceMethod(Builder<R, T> builder) {
    this.callFactory = builder.retrofit.callFactory();
    this.callAdapter = builder.callAdapter;
    this.baseUrl = builder.retrofit.baseUrl();
    this.responseConverter = builder.responseConverter;
    this.httpMethod = builder.httpMethod;
    this.relativeUrl = builder.relativeUrl;
    this.headers = builder.headers;
    this.contentType = builder.contentType;
    this.hasBody = builder.hasBody;
    this.isFormEncoded = builder.isFormEncoded;
    this.isMultipart = builder.isMultipart;
    this.parameterHandlers = builder.parameterHandlers;
  }

  /** Builds an HTTP request from method arguments. */
  Request toRequest(Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }

    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.build();
  }

  /** Builds a method return value from an HTTP response body. */
  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }
  //省略了Builder內(nèi)部類
}

通過錯略看過源代碼可以獲取如下信息:

  • ServiceMethod是final的夫壁,不能被繼承
  • ServiceMethod的構造函數(shù)既不是public拾枣,也是private的,所以只能在包內(nèi)創(chuàng)建ServiceMethod對象盒让。
  • ServiceMethod有一個靜態(tài)內(nèi)部類Builder梅肤。是final的,同樣不能被繼承邑茄,看到Builder凭语,大家一定知道了ServiceMethod是Builder模式,所以 ServiceMethod構造函數(shù)就一個入?yún)⑹荁uilder
  • 所有的變量均是final的
  • 一共有4個方法撩扒,兩個靜態(tài)的兩個非靜態(tài)的
  • boxIfPrimitive (Class<?> type) 靜態(tài)方法:明顯就是獲取8個基本類型對應的裝箱類
  • parsePathParameters (String path) 靜態(tài)方法: 保證url中的路徑參數(shù)(PathParameters)有且僅使用一次似扔,不會出現(xiàn)重復的路徑參數(shù)(PathParameters)。
2搓谆、源碼詳解:
2.1炒辉、變量解析:

大家先來看下變量,由于變量不多泉手,我直接上注釋了黔寇。

 //用來校驗的常量
static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
  static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
  static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);
  //網(wǎng)絡請求call的工廠,注釋是okhttp3的
  final okhttp3.Call.Factory callFactory;
  // 網(wǎng)絡請求適配器
  final CallAdapter<R, T> callAdapter;
  //基礎HttpUrl
  private final HttpUrl baseUrl;
  //響應轉化器
  private final Converter<ResponseBody, R> responseConverter;
   //網(wǎng)絡請求的方法
  private final String httpMethod;
  //網(wǎng)絡請求url的相對路徑
  private final String relativeUrl;
  // 請求頭
  private final Headers headers;
   //請求類型
  private final MediaType contentType;
  //是否有請求體
  private final boolean hasBody;
  //是否是表單提交
  private final boolean isFormEncoded;
  //以二進制流的方式提交
  private final boolean isMultipart;
  //參數(shù)處理器斩萌。具體在后面講解
  private final ParameterHandler<?>[] parameterHandlers;
2.2缝裤、ServiceMethod的兩個非靜態(tài)方法:
2.2.1首先來看下toRequest方法
  /** Builds an HTTP request from method arguments. */
  Request toRequest(Object... args) throws IOException {
    //new 了一個RequestBuilder對象
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);
    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    //獲取入?yún)⒌膫€數(shù)
    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }
    //遍歷入?yún)?    for (int p = 0; p < argumentCount; p++) {
      //配置對應的requestBuilder屬性
      handlers[p].apply(requestBuilder, args[p]);
    }
    //調(diào)用requestBuilder的build()方法獲取一個okhttp3.request對象
    return requestBuilder.build();
  }

通過注釋我們發(fā)現(xiàn)屏轰,其實這個方法是:

通過方法的入?yún)韷驅⒁粋€HTTP的請求。

方法內(nèi)的大體流程如下:

  • 1憋飞、new 了一個RequestBuilder對象
  • 2霎苗、 獲取入?yún)⒌膫€數(shù),并與自身對應的入?yún)⑻幚眍惖膫€數(shù)進行對比榛做,不一致拋異常唁盏,理論上應該是一致的,因為你的每個“方法”的每一個入?yún)⒍紝粋€注解检眯。
  • 3厘擂、遍歷這些入?yún)⑻幚眍悾_始配置requestBuilder屬性
  • 4锰瘸、配置完畢刽严,調(diào)用requestBuilder的.build()方法獲取一個okhttp3.Request對象
2.2.2 讓我們來看下toResponse方法
  /** Builds a method return value from an HTTP response body. */
  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

通過方法注釋我們得知:

通過一個HTTP的響應體(response body)來構建一個方法的返回值。

方法很簡單就一行避凝,就是調(diào)用響應解析/處理器(responseConverter)的convert方法來解析/反序列化響應體港庄。

3、靜態(tài)內(nèi)部類 ServiceMethod.Builder
3.1恕曲、先看下類的注釋
  /**
   * Inspects the annotations on an interface method to construct a reusable service method. This
   * requires potentially-expensive reflection so it is best to build each service method only once
   * and reuse it. Builders cannot be reused.
   */

翻譯一下就是

通過檢查接口方法上的注解來構建一個可以重用的 "服務方法"鹏氧,由于用到高性能消耗的反射,所以最好一個 "服務方法" 只構建一次佩谣,并且可以重復使用把还。Builders不能被重用

我的理解是:

其實Retrofit內(nèi)部是通過動態(tài)代理,內(nèi)部是通過反射實現(xiàn)的茸俭,大家都知道反射其實是很消耗性能的吊履,為了避免這種高性能的消耗,反射成功以后就緩存起來调鬓,下次如果有用到就重用艇炎,如果不能重用則用反射構建出來,但是Builder是不能重用的腾窝。

3.2缀踪、 ServiceMethod.Builder源碼解析

由于Builder源碼太多了,我就不上源碼了
我們來看下Builder類的變量虹脯,我直接標注釋了

3.2.1驴娃、ServiceMethod.Builder變量解析
    //Retrofit
    final Retrofit retrofit;
    //定義接口的抽象方法
    final Method method;
    //定義接口的抽象方法上面的注解數(shù)組
    final Annotation[] methodAnnotations;
     //注解二維數(shù)組:第一維對應參數(shù)序列,第二維對應注解序列循集;
    final Annotation[][] parameterAnnotationsArray;
     // 參數(shù)類型數(shù)組
    final Type[] parameterTypes;
    // 響應的類型
    Type responseType;
    //是否有用 @Field 或者FieldMap 注解
    boolean gotField;
     //是否 有用@Part 或者 @PartMap注解
    boolean gotPart;
    //是否有用 @body注解
    boolean gotBody;
     // 是否 有用@Path 注解
    boolean gotPath;
     //是否 有用@Query 或者@QueryName 注解
    boolean gotQuery;
     // 是否具有url
    boolean gotUrl;
     //http 請求的方法
    String httpMethod;
     //是否  需要請求體
    boolean hasBody;
    //是否 使用@FormUrlEncoded  注解
    boolean isFormEncoded;
    // 是否 使用了@Multipart  注解
    boolean isMultipart;
    // 相對路徑
    String relativeUrl;
     //請求頭
    Headers headers;
    //類型
    MediaType contentType;
     //相對路徑的url參數(shù)
    Set<String> relativeUrlParamNames;
     //參數(shù)處理數(shù)組
    ParameterHandler<?>[] parameterHandlers;
     //響應轉化器
    Converter<ResponseBody, T> responseConverter;
     //請求轉化器
    CallAdapter<T, R> callAdapter;
3.2.2唇敞、 Builder構造函數(shù)解析
    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }
  • 我們知道Builder的構造函數(shù)有兩個入?yún)ⅲ粋€是Retrofit對象本身,一個是Method 對象疆柔。
  • methodAnnotations咒精、parameterTypes、parameterAnnotationsArray這三個變量的值都是通過method的三個方法獲取的
PS:通過之前反射的學習我們知道Method.getAnnotations(); 是獲取方法上面對應的注解旷档。method.getGenericParameterTypes();獲取的是方法參數(shù)的類型模叙,里面帶有實際的參數(shù)類型。method.getParameterAnnotations()彬犯;獲取的是方法參數(shù)上面的注解,是一個二維數(shù)組查吊,第一個維度代表的是方法參數(shù)對應的下標谐区,比如,一個方法有3個參數(shù)逻卖,那0代表第一個參數(shù)宋列,1代表第二個參數(shù),2代表第三個參數(shù)评也。

通過構造函數(shù)我們知道Builder類里面的5個變量已經(jīng)被初始化了

3.2.3炼杖、 Builder的方法build()方法解析
public ServiceMethod build() {
      //通過調(diào)用createCallAdapter()方法獲取callAdapter
      callAdapter = createCallAdapter();
      //通過調(diào)用callAdapter的responseType方法獲取響應體類型
      responseType = callAdapter.responseType();
      if (responseType == Response.class || responseType == okhttp3.Response.class) {
        throw methodError("'"
            + Utils.getRawType(responseType).getName()
            + "' is not a valid response body type. Did you mean ResponseBody?");
      }
      //通過createResponseConverter()方法獲取響應轉化器
      responseConverter = createResponseConverter();
      //遍歷方法的注解
      for (Annotation annotation : methodAnnotations) {
        //解析方法注解
        parseMethodAnnotation(annotation);
      }
      //如果沒有HTTP請求的方法,則拋異常
      if (httpMethod == null) {
        throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
      }
      //在上面遍歷方法注解并解析方法注解的時候盗迟,hasBody坤邪,isMultipart,isFormEncoded等已經(jīng)完成賦值
      //如果沒有請求體
      if (!hasBody) {
         //但是 還是使用了@Multipart注解罚缕,使用了@Multipart 是一定有消息體的
        if (isMultipart) {
          throw methodError(
              "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
        }
         //但是 還是使用了 @FormEncoded艇纺,使用了@FormEncoded 是一定有消息體的
        if (isFormEncoded) {
          throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
              + "request body (e.g., @POST).");
        }
      }
      //如果方法 入?yún)⒌淖⒔猓拈L度也就是有多少個入?yún)⒂实闅v每個入?yún)淖⒔?      int parameterCount = parameterAnnotationsArray.length;
      //創(chuàng)建對應的數(shù)量的參數(shù)處理類數(shù)組黔衡,這時候開始處理入?yún)淖⒔饬?      parameterHandlers = new ParameterHandler<?>[parameterCount];
      //遍歷入?yún)⒌臄?shù)量
      for (int p = 0; p < parameterCount; p++) {
         //首先獲取入?yún)⒌念愋?        Type parameterType = parameterTypes[p];
         //如果是不能處理的類型,則拋異常
        if (Utils.hasUnresolvableType(parameterType)) {
          throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
              parameterType);
        }
        //如果是可以處理的類型獲取對應位置入?yún)⒌淖⒔?        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        //如果對應入?yún)⒌淖⒔鉃閚ull腌乡,則拋異常
        if (parameterAnnotations == null) {
          throw parameterError(p, "No Retrofit annotation found.");
        }
         //如果對應的入?yún)⒆⒔獠粸閚ull,則調(diào)用parseParameter方法獲取ParameterHandler盟劫,cancel在這里方法里面創(chuàng)建ParameterHandler里面的靜態(tài)內(nèi)部類,后面咱們再仔細看下与纽。
        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }
       //for循環(huán)遍歷以后侣签,如果relativeUrl還為null,同時沒有使用急迂!@url注解硝岗,這樣的話我們就無法獲取url地址了,所以要拋異常袋毙。
      if (relativeUrl == null && !gotUrl) {
        throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
      }
      //如果沒有使用@FormEncoded注解型檀、@Multipart和,同時HTTP請求方式也不需要響應提听盖,但是卻使用了@Body 注解胀溺,這不是自相矛盾裂七,所以拋異常。
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError("Non-body HTTP method cannot contain @Body.");
      }
      //如果使用@FormEncoded注解仓坞,卻沒使用@Field注解背零,不符合規(guī)范,則拋異常
      if (isFormEncoded && !gotField) {
        throw methodError("Form-encoded method must contain at least one @Field.");
      }
      //如果使用 @Multipart 注解无埃,卻沒有使用@Part 注解徙瓶,拋異常
      if (isMultipart && !gotPart) {
        throw methodError("Multipart method must contain at least one @Part.");
      }
      //走到這里說明沒有問題,則new一個ServiceMethod對象嫉称,入?yún)⑹荁uilder自己侦镇,然后return這個ServiceMethod對象。
      return new ServiceMethod<>(this);
    }

大體流程如下:

  • 1织阅、通過createCallAdapter()方法取得callAdapter對象
  • 2壳繁、通過callAdapter的responseType()方法獲取響應對應的類型
  • 3、通過createResponseConverter()來獲取響應轉化/解析器
  • 4荔棉、遍歷方法上的注解闹炉,并解析注解
  • 5、判斷是GET還是POST或者其他HTTP請求方式
  • 6润樱、異常邏輯判斷渣触,如果HTTP請求方式不需要消息體,但是卻使用@FormEncoded注解壹若、@Multipart昵观,自相矛盾。
  • 7舌稀、獲取這個方法入?yún)⒌膫€數(shù)
  • 8啊犬、遍歷這個方法的各個入?yún)ⅲ紫扰袛嗍遣皇悄芴幚淼念愋捅诓椋绻悄芴幚淼念愋途踔粒蝗缓螳@取這個入?yún)淖⒔猓蛔詈笳{(diào)用parseParameter()來獲取對應的parameterHandlers睡腿。
  • 9语御、異常邏輯判斷,比如如果沒有相對路徑還沒有使用@Url席怪,我們會無法得到具體的地址应闯。
  • 10、異常邏輯處理挂捻,既沒有使用@FormUrlEncode 注解也沒有使用@Multipart注解碉纺,且HTTP請求方式也需要使用請求,卻使用@Body 注解,違反規(guī)范骨田,拋異常
  • 11耿导、異常邏輯處理,使用@FormUrlEncoded注解态贤,但是卻沒有使用@Field注解舱呻,這是違背Retrofit的規(guī)定的。
  • 12悠汽、異常邏輯處理箱吕,使用了@Multipart 注解,卻沒有使用@Part注解柿冲,同樣是違背Retrofit的規(guī)定的茬高。
  • 13、最后new了一個ServiceMethod對象姻采,并return

自此整個build流程已經(jīng)全部解析完畢雅采,那我們在看來里面設計的其它方法

3.2.4爵憎、 Builder的方法createCallAdapter解析

通過方法字面的含義我們理解為這個方法創(chuàng)建CallAdatper慨亲,具體我們看下源碼:

    private CallAdapter<T, R> createCallAdapter() {
       //獲取方法的返回值類型
      Type returnType = method.getGenericReturnType();
       //如果方法的返回值類型我們無法處理,則拋出異常
      if (Utils.hasUnresolvableType(returnType)) {
        throw methodError(
            "Method return type must not include a type variable or wildcard: %s", returnType);
      }
      //如果方法是void的宝鼓,則沒有返回值
      if (returnType == void.class) {
        throw methodError("Service methods cannot return void.");
      }
      //獲取方法的注解
      Annotation[] annotations = method.getAnnotations();
      try {
        //調(diào)用retrofit的callAdapter來獲取一個CallAdapter對象
        //noinspection unchecked
        return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
      } catch (RuntimeException e) { // Wide exception range because factories are user code.
        throw methodError(e, "Unable to create call adapter for %s", returnType);
      }
    }

大體流程如下:

  • 1刑棵、通過method.getGenericReturnType()獲取返回值類型
  • 2、排除我們不能處理的類型
  • 3愚铡、排除無返回值(void)的情況
  • 4蛉签、調(diào)用 retrofit的callAdapter的方法來獲取一個CallAdapter對象,這個里面的內(nèi)部調(diào)用沥寥,我們一會再說碍舍。
3.2.5、 Builder的方法parseMethodAnnotation解析

這個方法看字面的意思就知道是一個處理方法的注解,具體以源碼為準邑雅。

 private void parseMethodAnnotation(Annotation annotation) {
      //如果是DELETE注解 代表DELETE請求
      if (annotation instanceof DELETE) {
         //調(diào)用parseHttpMethodAndPath()
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
        //如果是GET 注解片橡,則代表是get請求
      } else if (annotation instanceof GET) {
       //調(diào)用parseHttpMethodAndPath()
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
        //如果是 HEAD注解,則代表HEAD請求淮野,這里不是請求頭,HEAD方法跟GET方法相同捧书,只不過服務器響應時不會返回消息體。
      } else if (annotation instanceof HEAD) {
          //調(diào)用parseHttpMethodAndPath()
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
        if (!Void.class.equals(responseType)) {
           //HEAD請求是沒有響應體的
          throw methodError("HEAD method must use Void as response type.");
        }
        //如果是PATCH請求
      } else if (annotation instanceof PATCH) {
          //調(diào)用parseHttpMethodAndPath()
        parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
      } else if (annotation instanceof POST) {
        //調(diào)用parseHttpMethodAndPath()
        parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
       //如果是PUT請求
      } else if (annotation instanceof PUT) {
        //調(diào)用parseHttpMethodAndPath()
        parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
         //如果是 OPTIONS注解骤星,代表OPTIONS請求
      } else if (annotation instanceof OPTIONS) {
          //調(diào)用parseHttpMethodAndPath()
        parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
          //如果是@HTTP 注解经瓷,則代表自定義 HTTP請求
      } else if (annotation instanceof HTTP) {
        HTTP http = (HTTP) annotation;
            //調(diào)用parseHttpMethodAndPath()
        parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
         //如果是 注解Headers 則代表請求頭
      } else if (annotation instanceof retrofit2.http.Headers) {
        //獲取對應的String 數(shù)組
        String[] headersToParse = ((retrofit2.http.Headers) annotation).value(); 
         //判斷數(shù)組長度
        if (headersToParse.length == 0) {
          //這里可見如果一定使用@Headers 注解,則一定要有內(nèi)容洞难,否則會報錯舆吮。
          throw methodError("@Headers annotation is empty.");
        }
         //調(diào)用parseHeaders()方法來解析headersToParse
        headers = parseHeaders(headersToParse);
         //如果 是Multipart注解,則代表用文件提交
      } else if (annotation instanceof Multipart) {
         //如果是文件提交,則不能使用表單提交歪泳,這是互斥的
        if (isFormEncoded) {
          throw methodError("Only one encoding annotation is allowed.");
        }
         //給isMultipart賦值為true
        isMultipart = true;
         //如果 是 FormUrlEncoded注解萝勤,則代表表單提交
      } else if (annotation instanceof FormUrlEncoded) {
         由于和文件提交互斥,要判斷
        if (isMultipart) {
          throw methodError("Only one encoding annotation is allowed.");
        }
        //給isFormEncoded賦值
        isFormEncoded = true;
      }
    }

這個沒什么流程呐伞,就是不斷if else的過程敌卓,其中先是判斷注解的類型,根據(jù)不同的類型來調(diào)用parseHttpMethodAndPath方法伶氢,這里主要指的是DELETE趟径、GET、HEAD癣防、PATCH蜗巧、POST、PUT蕾盯、OPTIONS請求幕屹,當然也包括自定義的HTTP請求。然后判斷如果Headers级遭,則說明要向請求頭里面添加數(shù)據(jù)望拖。最后做了表單提交和二進制流提交的互斥。那我們接來下就來看下對應的parseHttpMethodAndPath()方法挫鸽。

3.2.6说敏、 Builder的方法parseHttpMethodAndPath解析

通過方法名,我們理解這個方法主要是"解析HTTP的請求方法和路徑"丢郊,我們一起來看下源碼

 private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
      //因為Http請求只能設置一種請求方式盔沫,所以先做判斷是否已經(jīng)設置過請求方式,如果設置過則不能重復設置
      if (this.httpMethod != null) {
        throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
            this.httpMethod, httpMethod);
      }
      //設置請求方式
      this.httpMethod = httpMethod;
      //根據(jù)不同的請求方式來設置是否需要請求體
      this.hasBody = hasBody;
       //如果注解中沒有內(nèi)容枫匾,則返回架诞,因為不需要處理
      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.
        //確保這個查詢字符沒有對應的參數(shù)
        //獲取'?'字符后面的字符串
        String queryParams = value.substring(question + 1);
         //根據(jù)正則表達來匹配
        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);
        }
      }
      //把value設置為相對路徑的值
      this.relativeUrl = value;
       //最后調(diào)用parsePathParameters方法來放置出現(xiàn)重復參數(shù)
      this.relativeUrlParamNames = parsePathParameters(value);
    }

大體流程如下:

  • 1谴忧、保證請求方式只設置一次
  • 2、設置請求方式和是否需要請求體
  • 3等脂、如果value值為空則return
  • 4俏蛮、判斷value是否有 '?' 查詢字段,有的則根據(jù)正則表達來判斷上遥,如果是符合PARAM_URL_REGEX規(guī)則則拋異常搏屑。
3.2.7、 Builder的方法parseHeaders解析

這個方法看方法名粉楚,可以理解解析請求頭

    private Headers parseHeaders(String[] headers) {
      //new 了一個Headers.Builder()對象
      Headers.Builder builder = new Headers.Builder();
       //遍歷 字符串數(shù)組
      for (String header : headers) {
        獲取':'的position下標
        int colon = header.indexOf(':');
        //如果不存在字符':',或者在最前面辣恋,或者在最后面 這是不符合header標準亮垫,所以拋異常
        if (colon == -1 || colon == 0 || colon == header.length() - 1) {
          throw methodError(
              "@Headers value must be in the form \"Name: Value\". Found: \"%s\"", header);
        }
        //按照':'分類,在':'前面為name伟骨,在':'后面為value
        String headerName = header.substring(0, colon);
        String headerValue = header.substring(colon + 1).trim();
         //按照忽略大小寫的方法來判斷是否是"Content-Type"饮潦。
        if ("Content-Type".equalsIgnoreCase(headerName)) {
          如果是"Content-Type",則來獲取類型
          MediaType type = MediaType.parse(headerValue);
           //類型不能為null
          if (type == null) {
            throw methodError("Malformed content type: %s", headerValue);
          }
          把類型賦給contentType
          contentType = type;
        } else {
          //如果不是"Content-Type"携狭,則添加到 Headers.Builder 里面
          builder.add(headerName, headerValue);
        }
      }
      //調(diào)用Headers.Builder的build()方法來返回一個okhttp3.Headers對象
      return builder.build();
    }
  • 1继蜡、new 一個Headers.Builder()對象
  • 2、for each遍歷headersString數(shù)組
  • 3逛腿、以':'為分界線前面為key稀并,后面是value
  • 4、如果key是"Content-Type"单默,則獲取響應的類型
  • 5碘举、把key和value作為一對,添加到builder中
  • 6搁廓、調(diào)用builder的build()來獲取一個okhttp3.Headers對象引颈,并把它返回。

由于受到簡書的篇幅限制境蜕,剩下的內(nèi)容我們將在下一篇文章中解讀

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蝙场,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子汽摹,更是在濱河造成了極大的恐慌李丰,老刑警劉巖苦锨,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逼泣,死亡現(xiàn)場離奇詭異,居然都是意外死亡舟舒,警方通過查閱死者的電腦和手機拉庶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秃励,“玉大人氏仗,你說我怎么就攤上這事《嵯剩” “怎么了皆尔?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長币励。 經(jīng)常有香客問我慷蠕,道長,這世上最難降的妖魔是什么食呻? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任流炕,我火速辦了婚禮澎现,結果婚禮上,老公的妹妹穿的比我還像新娘每辟。我一直安慰自己剑辫,他們只是感情好,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布渠欺。 她就那樣靜靜地躺著妹蔽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挠将。 梳的紋絲不亂的頭發(fā)上讹开,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音捐名,去河邊找鬼旦万。 笑死,一個胖子當著我的面吹牛镶蹋,可吹牛的內(nèi)容都是我干的成艘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼贺归,長吁一口氣:“原來是場噩夢啊……” “哼淆两!你這毒婦竟也來了?” 一聲冷哼從身側響起拂酣,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤秋冰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后婶熬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剑勾,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年赵颅,在試婚紗的時候發(fā)現(xiàn)自己被綠了虽另。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡饺谬,死狀恐怖捂刺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情募寨,我是刑警寧澤族展,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站拔鹰,受9級特大地震影響仪缸,放射性物質發(fā)生泄漏。R本人自食惡果不足惜格郁,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一腹殿、第九天 我趴在偏房一處隱蔽的房頂上張望独悴。 院中可真熱鬧,春花似錦锣尉、人聲如沸刻炒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坟奥。三九已至,卻和暖如春拇厢,著一層夾襖步出監(jiān)牢的瞬間爱谁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工孝偎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留访敌,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓衣盾,卻偏偏與公主長得像寺旺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子势决,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容