Retrofit2.5是如何解析在接口類中定義的方法土匀?

前言

Retrofit的核心在于它的create方法中使用了動(dòng)態(tài)代理淑履,在這里面主要是loadServiceMethod方法:

以下代碼基于Retrofit2.5.0(跟2.3.0代碼存在明顯不同)

  public <T> T create(final Class<T> service) {
    //省略無關(guān)代碼
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          private final Object[] emptyArgs = new Object[0];

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            //省略無關(guān)代碼
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }

請求方法的解析

首先來看loadServiceMethod方法:

Retrofit.loadServiceMethod(Method method)

private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();

  ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method);//解析方法
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

serviceMethodCache是一個(gè)緩存的Map米苹,這個(gè)方法主要就是執(zhí)行了ServiceMethod.parseAnnotations

先看看返回的類ServiceMethod

這個(gè)方法返回ServiceMethod這個(gè)類:

abstract class ServiceMethod<T> {//抽象類
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {//解析注解
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);//請求工廠初始化

    Type returnType = method.getGenericReturnType();//獲取方法的返回類型
    //省略無關(guān)代碼
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);//返回解析結(jié)果
  }

  abstract T invoke(Object[] args);
}

繼續(xù)看RequestFactory.parseAnnotations(retrofit, method)

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }

在這里又用到了創(chuàng)建者方法來創(chuàng)建這個(gè)請求工廠公般,進(jìn)入Builder:

    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;//當(dāng)前的retrofit實(shí)例
      this.method = method;//請求的方法
      this.methodAnnotations = method.getAnnotations();//注解
      this.parameterTypes = method.getGenericParameterTypes();//參數(shù)的類型集合
      this.parameterAnnotationsArray = method.getParameterAnnotations();//參數(shù)的注解集合
    }

可以看到這個(gè)創(chuàng)建類里面包含了我們在接口定義的方法的所有信息臼疫,包括注解和參數(shù)择份,再繼續(xù)來看它的build方法做了什么:

    RequestFactory build() {
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);//重點(diǎn)1.解析方法上面的注解
      }
        //省略無關(guān)代碼
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p]);//重點(diǎn)2.解析方法的參數(shù)
      }
      //省略無關(guān)代碼
      return new RequestFactory(this);
    }

在這里,進(jìn)行了對方法的具體解析烫堤,主要是兩個(gè)步驟

  1. 解析方法上面的注解parseMethodAnnotation
  2. 解析方法的請求參數(shù)注解parseParameter

到兩個(gè)方法里面看看:

方法上面的注解

RequestFactory.parseMethodAnnotation

    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);
      } 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(method, "@Headers annotation is empty.");
        }
        headers = parseHeaders(headersToParse);
      } else if (annotation instanceof Multipart) {
        if (isFormEncoded) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isMultipart = true;
      } else if (annotation instanceof FormUrlEncoded) {
        if (isMultipart) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
      }
    }

在這里荣赶,看到了熟悉的一幕,我們平常使用Retrofit時(shí)在方法上面使用的@POST和@GET之類的注解鸽斟,就是在這個(gè)方法里面進(jìn)行的解析拔创,這里先做一個(gè)判斷,繼續(xù)調(diào)用parseHttpMethodAndPath

RequestFactory.parseHttpMethodAndPath

    private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
      if (this.httpMethod != null) {
        throw methodError(method, "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(method, "URL query string \"%s\" must not have replace block. "
              + "For dynamic query parameters use @Query.", queryParams);
        }
      }

      this.relativeUrl = value;//相對請求地址
      this.relativeUrlParamNames = parsePathParameters(value);//解析請求鏈接里面的參數(shù)
    }

在這里進(jìn)行了關(guān)于請求的方法等相關(guān)屬性的賦值富蓄,最后調(diào)用parsePathParameters解析我們在注解里面?zhèn)魅氲牡刂返膮?shù):

RequestFactory.parsePathParameters

    private static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
    private static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
    static Set<String> parsePathParameters(String path) {
      Matcher m = PARAM_URL_REGEX.matcher(path);
      Set<String> patterns = new LinkedHashSet<>();
      while (m.find()) {
        patterns.add(m.group(1));
      }
      return patterns;
    }

這里應(yīng)該都能看懂剩燥,使用正則表達(dá)式來匹配。

到這里立倍,完成了對方法上面的注解的解析灭红,接下來侣滩,進(jìn)行對方法的參數(shù)的解析:

方法的請求參數(shù)注解

RequestFactory.parseParameter

    private ParameterHandler<?> parseParameter(
        int p, Type parameterType, @Nullable Annotation[] annotations) {
      ParameterHandler<?> result = null;
      if (annotations != null) {
        for (Annotation annotation : annotations) {
          ParameterHandler<?> annotationAction =
              parseParameterAnnotation(p, parameterType, annotations, annotation);//參數(shù)解析

          //省略無關(guān)代碼

          result = annotationAction;
        }
      }

      //省略無關(guān)代碼
      return result;
    }

返回的ParameterHandler類為參數(shù)的句柄,這里是一個(gè)抽象類变擒,里面有Header君珠,PathQuery等跟我們在參數(shù)前面加的標(biāo)注同名的實(shí)現(xiàn)類赁项。

使用一個(gè)循環(huán)葛躏,來解析每個(gè)參數(shù)的注解,這里調(diào)用了parseParameterAnnotation方法,這個(gè)方法跟剛才解析方法上面的注解parseMethodAnnotation很像悠菜,里面進(jìn)行了很多判斷,在參數(shù)里面可以加的注解很多败富,所以方法太長悔醋,這里看一看我們經(jīng)常用到的GET請求的@Query注解的解析:

RequestFactory.parseParameterAnnotation

    //省略無關(guān)代碼
      } else if (annotation instanceof Query) {
        validateResolvableType(p, type);
        Query query = (Query) annotation;
        String name = query.value();
        boolean encoded = query.encoded();

        Class<?> rawParameterType = Utils.getRawType(type);//獲取類型
        gotQuery = true;
        if (Iterable.class.isAssignableFrom(rawParameterType)) {//是否是集合
          if (!(type instanceof ParameterizedType)) {
            throw parameterError(method, p, rawParameterType.getSimpleName()
                + " must include generic type (e.g., "
                + rawParameterType.getSimpleName()
                + "<String>)");
          }
          ParameterizedType parameterizedType = (ParameterizedType) type;
          Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
          Converter<?, String> converter =
              retrofit.stringConverter(iterableType, annotations);
          return new ParameterHandler.Query<>(name, converter, encoded).iterable();
        } else if (rawParameterType.isArray()) {//是否是集合的數(shù)組
          Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
          Converter<?, String> converter =
              retrofit.stringConverter(arrayComponentType, annotations);
          return new ParameterHandler.Query<>(name, converter, encoded).array();
        } else {//普通類型
          Converter<?, String> converter =
              retrofit.stringConverter(type, annotations);
          return new ParameterHandler.Query<>(name, converter, encoded);
        }

      } else if (annotation instanceof QueryName) {
      //省略無關(guān)代碼

在這個(gè)方法里面進(jìn)行了類型和泛型相關(guān)的判斷,里面都調(diào)用了retrofit.stringConverter方法:

retrofit.stringConverter

  public <T> Converter<T, String> stringConverter(Type type, Annotation[] annotations) {
    checkNotNull(type, "type == null");
    checkNotNull(annotations, "annotations == null");

    for (int i = 0, count = converterFactories.size(); i < count; i++) {
      Converter<?, String> converter =
          converterFactories.get(i).stringConverter(type, annotations, this);
      if (converter != null) {
        //noinspection unchecked
        return (Converter<T, String>) converter;
      }
    }

    // Nothing matched. Resort to default converter which just calls toString().
    //noinspection unchecked
    return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE;
  }

這個(gè)方法的作用是獲取請求參數(shù)的json解析器兽叮,一個(gè)循環(huán)從converterFactories數(shù)組中依次獲取加入的解析器工廠芬骄,我們在之前創(chuàng)建Retrofit傳入的是GsonConverterFactory,打開這個(gè)類鹦聪,并沒有發(fā)現(xiàn)stringConverter方法账阻,再打開它的父類Converter.Factory,看到了這個(gè)方法:

    public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

返回的是空泽本,說明這里不需要使用解析工廠將請求的參數(shù)轉(zhuǎn)化為String淘太。所以直接調(diào)用最后一句

return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE;
  static final class ToStringConverter implements Converter<Object, String> {
    static final ToStringConverter INSTANCE = new ToStringConverter();

    @Override public String convert(Object value) {
      return value.toString();
    }
  }

返回的是一個(gè)默認(rèn)的轉(zhuǎn)化類,在這個(gè)類的convert方法使用的是類自身的toString來轉(zhuǎn)化规丽。

到此為止蒲牧,就完成了我們在接口中定義的那個(gè)方法的全部解析。

總結(jié)

Retrofit使用了動(dòng)態(tài)代理赌莺,所以每次執(zhí)行我們在接口中定義的方法會(huì)來到動(dòng)態(tài)代理中的invoke方法冰抢,在這里面,又執(zhí)行了loadServiceMethod來實(shí)現(xiàn)對方法的解析艘狭,主要是兩個(gè)步驟:

  • 解析方法上面的注解(如@Headers挎扰,@POST,@GET)
  • 解析方法的請求參數(shù)前面的注解(如@Query, @Field)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巢音,一起剝皮案震驚了整個(gè)濱河市遵倦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌港谊,老刑警劉巖骇吭,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歧寺,居然都是意外死亡燥狰,警方通過查閱死者的電腦和手機(jī)棘脐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來龙致,“玉大人蛀缝,你說我怎么就攤上這事∧看” “怎么了屈梁?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長榛了。 經(jīng)常有香客問我在讶,道長,這世上最難降的妖魔是什么霜大? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任构哺,我火速辦了婚禮,結(jié)果婚禮上战坤,老公的妹妹穿的比我還像新娘曙强。我一直安慰自己,他們只是感情好途茫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布碟嘴。 她就那樣靜靜地躺著,像睡著了一般囊卜。 火紅的嫁衣襯著肌膚如雪娜扇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天边败,我揣著相機(jī)與錄音袱衷,去河邊找鬼。 笑死笑窜,一個(gè)胖子當(dāng)著我的面吹牛致燥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播排截,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嫌蚤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了断傲?” 一聲冷哼從身側(cè)響起脱吱,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎认罩,沒想到半個(gè)月后箱蝠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年宦搬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牙瓢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡间校,死狀恐怖矾克,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情憔足,我是刑警寧澤胁附,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站滓彰,受9級特大地震影響控妻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜揭绑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一饼暑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洗做,春花似錦、人聲如沸彰居。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陈惰。三九已至畦徘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抬闯,已是汗流浹背井辆。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溶握,地道東北人杯缺。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像睡榆,于是被迫代替她去往敵國和親萍肆。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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

  • 前言 注解式的框架非痴陀欤火塘揣,注解以其輕量,簡潔等特性被人們所喜愛者宿崭,關(guān)鍵是它解藕亲铡。網(wǎng)絡(luò)請求的框架非常多,比較受歡迎的...
    薩達(dá)哈魯醬閱讀 580評論 0 5
  • 一、什么是Retrofit A type-safe HTTP client for Android and Jav...
    andcoder閱讀 770評論 2 3
  • 前言 Retrofit這個(gè)強(qiáng)大的網(wǎng)絡(luò)請求庫相信每個(gè)做安卓的小伙伴們都不陌生奖蔓,通過簡單的構(gòu)造之后赞草,只需要在creat...
    李曉通閱讀 942評論 7 8
  • Retrofit是非常知名的輪子了, 幾年之前很火的就是Retrofit + Rxjava了.相比單獨(dú)的OkHtt...
    ukyoo閱讀 471評論 0 3
  • 現(xiàn)在人的身體素質(zhì)下降房资,各種亞健康狀況出現(xiàn),二十多歲的年輕人頸椎病檀头,腰腿痛轰异,心腦血管疾病都可以出現(xiàn),還有高血壓糖...
    開心的靈通閱讀 940評論 0 0