Retrofit2.5是如何解析在介面類中定義的請求方法?

SillyMonkey發表於2019-04-09

前言

Retrofit的核心在於它的create方法中使用了動態代理,在這裡面主要是loadServiceMethod方法:

以下程式碼基於Retrofit2.5.0(跟2.3.0程式碼存在明顯不同)

  public <T> T create(final Class<T> service) {
    //省略無關程式碼
    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 {
            //省略無關程式碼
            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是一個快取的Map,這個方法主要就是執行了ServiceMethod.parseAnnotations

先看看返回的類ServiceMethod

這個方法返回ServiceMethod這個類:

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

    Type returnType = method.getGenericReturnType();//獲取方法的返回型別
    //省略無關程式碼
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);//返回解析結果
  }

  abstract T invoke(Object[] args);
}
複製程式碼

繼續看RequestFactory.parseAnnotations(retrofit, method)

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }
複製程式碼

在這裡又用到了建立者方法來建立這個請求工廠,進入Builder:

    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;//當前的retrofit例項
      this.method = method;//請求的方法
      this.methodAnnotations = method.getAnnotations();//註解
      this.parameterTypes = method.getGenericParameterTypes();//引數的型別集合
      this.parameterAnnotationsArray = method.getParameterAnnotations();//引數的註解集合
    }
複製程式碼

可以看到這個建立類裡面包含了我們在介面定義的方法的所有資訊,包括註解和引數,再繼續來看它的build方法做了什麼:

    RequestFactory build() {
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);//重點1.解析方法上面的註解
      }
        //省略無關程式碼
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p]);//重點2.解析方法的引數
      }
      //省略無關程式碼
      return new RequestFactory(this);
    }
複製程式碼

在這裡,進行了對方法的具體解析,主要是兩個步驟

  1. 解析方法上面的註解parseMethodAnnotation
  2. 解析方法的請求引數註解parseParameter

到兩個方法裡面看看:

方法上面的註解

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時在方法上面使用的@POST和@GET之類的註解,就是在這個方法裡面進行的解析,這裡先做一個判斷,繼續呼叫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);//解析請求連結裡面的引數
    }
複製程式碼

在這裡進行了關於請求的方法等相關屬性的賦值,最後呼叫parsePathParameters解析我們在註解裡面傳入的地址的引數:

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;
    }
複製程式碼

這裡應該都能看懂,使用正規表示式來匹配。

到這裡,完成了對方法上面的註解的解析,接下來,進行對方法的引數的解析:

方法的請求引數註解

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);//引數解析

          //省略無關程式碼

          result = annotationAction;
        }
      }

      //省略無關程式碼
      return result;
    }
複製程式碼

返回的ParameterHandler類為引數的控制程式碼,這裡是一個抽象類,裡面有HeaderPathQuery等跟我們在引數前面加的標註同名的實現類。

使用一個迴圈,來解析每個引數的註解,這裡呼叫了parseParameterAnnotation方法,這個方法跟剛才解析方法上面的註解parseMethodAnnotation很像,裡面進行了很多判斷,在引數裡面可以加的註解很多,所以方法太長,這裡看一看我們經常用到的GET請求的@Query註解的解析:

RequestFactory.parseParameterAnnotation

    //省略無關程式碼
      } 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()) {//是否是集合的陣列
          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) {
      //省略無關程式碼
複製程式碼

在這個方法裡面進行了型別和泛型相關的判斷,裡面都呼叫了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;
  }
複製程式碼

這個方法的作用是獲取請求引數的json解析器,一個迴圈從converterFactories陣列中依次獲取加入的解析器工廠,我們在之前建立Retrofit傳入的是GsonConverterFactory,開啟這個類,並沒有發現stringConverter方法,再開啟它的父類Converter.Factory,看到了這個方法:

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

返回的是空,說明這裡不需要使用解析工廠將請求的引數轉化為String。所以直接呼叫最後一句

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();
    }
  }
複製程式碼

返回的是一個預設的轉化類,在這個類的convert方法使用的是類自身的toString來轉化。

到此為止,就完成了我們在介面中定義的那個方法的全部解析。

總結

Retrofit使用了動態代理,所以每次執行我們在介面中定義的方法會來到動態代理中的invoke方法,在這裡面,又執行了loadServiceMethod來實現對方法的解析,主要是兩個步驟:

  • 解析方法上面的註解(如@Headers,@POST,@GET)
  • 解析方法的請求引數前面的註解(如@Query, @Field)

相關文章