Retrofit源码赏析七 ——RequestFactory

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情

在ServiceMethod类中,我们通过RequestFactory.parseAnnotations()获取RequestFactory对象

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
	RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
}

RequestFactory

RequestFactory是生产Request的地方,他使用构造者模式创建

final class RequestFactory {
    static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
        return new Builder(retrofit, method).build();
    }
    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }
}
  • getAnnotations() 获取方法上的注解
  • getGenericParameterTypes() 获取方法的参数类型
  • getParameterAnnotations() 获取方法的参数上的注解

以下面这个方法为例

@POST("xxx")
fun tesAnnotation(@Query("age") age: Int):Call<Optional<String>>

测试输出结果为

if(method.getName().equals("tesAnnotation")){
    System.out.println(Arrays.toString(method.getAnnotations()));               -------------->     [@retrofit2.http.POST(value="xxx")]
    System.out.println(Arrays.toString(method.getGenericParameterTypes()));     -------------->     [int]
    System.out.println(Arrays.toString(method.getParameterAnnotations()[0]));   -------------->     [@retrofit2.http.Query(encoded=false, value="age")]
}

需要注意的是getParameterAnnotations()返回的结果是一个二维数组,因为例子中只有一个参数,所以直接取第一个元素。

创建RequestFactory

RequestFactory build() {
    //1 解析方法上的注解
    for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
    } 
    //2 检查方法上的注解(省略部分代码)
    throw methodError("")
    //3 解析参数
    int parameterCount = parameterAnnotationsArray.length;
    parameterHandlers = new ParameterHandler<?>[parameterCount];
    for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
    parameterHandlers[p] =
        parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
    }
    //4 检查参数上的注解(省略部分代码)
    throw methodError("")
    //5 创建RequestFactory
    return new RequestFactory(this);
}

build()方法主要做了下面五件事

  1. 解析方法上的注解
  2. 检查方法上的注解
  3. 解析参数获取参数上的注解
  4. 检查参数上的注解
  5. 创建RequestFactory

解析方法上的注解

使用过Retrofit的朋友都知道,在APIService方法上的注解不仅可以是HTTP请求方式的注解,还能是@Headers,@FormUrlEncoded等,parseMethodAnnotation()就是专门来处理这些注解的。

private void parseMethodAnnotation(Annotation annotation) {
    if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
        //省略POST PUT等注解
    } 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;
    }
}

parseMethodAnnotation()主要做了注解分类,对于像GET,POST这些HTTP请求方式的注解都交由parseHttpMethodAndPath()处理,@Headers由parseHeaders()处理,最后是@Multipart和@FormUrlEncoded两个注解,我们可以看到,他们2个最多只能2选1.,否则会抛出异常。

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;}
    int question = value.indexOf('?');
    if (question != -1 && question < value.length() - 1) {
        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);
}
  1. 首先检查当前方法HTTP请求方式的注解是否唯一,因为我们一次只能以一种请求方式访问服务器。
  2. 然后检查注解的参数,一般是URL地址,我们知道,?是用来分隔实际的URL和参数,如果URL中存在?,则动态查询参数不能搭配@Path使用,这时候应该使用@Query。
  3. 如果有动态查询参数的话,通过parsePathParameters()查找后放在relativeUrlParamNames中保存,查询过程与上一步检查参数一致,都是使用PARAM_URL_REGEX正则。relativeUrlParamNames是Set类型,所以参数会被去重。

parseHeaders

parseHeaders()方法主要是对传入的Header字符串检测并处理成Headers对象。

private Headers parseHeaders(String[] headers) {
    Headers.Builder builder = new Headers.Builder();
    for (String header : headers) {
        int colon = header.indexOf(':');
        if (colon == -1 || colon == 0 || colon == header.length() - 1) {
            throw methodError(method, "@Headers value must be in the form \"Name: Value\". Found: \"%s\"", header);
        }
        String headerName = header.substring(0, colon);
        String headerValue = header.substring(colon + 1).trim();
        if ("Content-Type".equalsIgnoreCase(headerName)) {
            try {
                contentType = MediaType.get(headerValue);
            } catch (IllegalArgumentException e) {
                throw methodError(method, e, "Malformed content type: %s", headerValue);
            }
        } else {
            builder.add(headerName, headerValue);
        }
    }
    return builder.build();
}

首先会检查请求头,请求头的规范是Name: Value,如果不存在字符':',或者以他开始或者结尾 都不符合header标准,直接抛出异常。对于Content-Type类型的Header解析成功后存放在了contentType中,其余的都用来构建Headers对象。

方法参数注解检查

if (httpMethod == null) {
    throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
    if (isMultipart) {
      throw methodError(method,"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
    }
    if (isFormEncoded) {
        throw methodError(method, "FormUrlEncoded can only be specified on HTTP methods with "+ "request body (e.g., @POST).");
    }
}
  1. APIService方法必须要有一个HTTP方法注解。
  2. 如果使用了@Multipart或者@FormUrlEncoded,则必须使用带有请求体的HTTP方法注解,例如@Patch,@Post,@Put。

参数解析

int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
    parameterHandlers[p] =  parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}

parameterAnnotationsArray是一个二维数组Annotation[][],存放的是method.getParameterAnnotations()获取的结果。第一维是参数的注解(可能为null),这里会给每一个注解创建一个ParameterHandler

parseParameter

private @Nullable ParameterHandler<?> parseParameter(int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
    ParameterHandler<?> result = null;
    if (annotations != null) {
        for (Annotation annotation : annotations) {
            ParameterHandler<?> annotationAction = parseParameterAnnotation(p, parameterType, annotations, annotation);
            if (annotationAction == null) {
               continue;
            }
            if (result != null) {
               throw parameterError(method, p, "Multiple Retrofit annotations found, only one allowed.");
            }
            result = annotationAction;
        }
   }
   if (result == null) {
      if (allowContinuation) {
         try {
            if (Utils.getRawType(parameterType) == Continuation.class) {
               isKotlinSuspendFunction = true;
               return null;
            }
         } catch (NoClassDefFoundError ignored) {
         }
      }
      throw parameterError(method, p, "No Retrofit annotation found.");
   }
   return result;
}

如果参数有注解,则由parseParameterAnnotation()方法解析并且转化为ParameterHandler对象,同一个参数最多只能有一个Retrofit注解

如果没有Retrofit注解,先判断当前参数是不是Continuation对象,因为kotlin挂起函数在转化为java方法的时候,会在方法最后添加一个Continuation参数,这种情况下可以没有Retrofit注解,其余情况必须要有一个。

parseParameterAnnotation()方法主要是对@Url,@Path,@Query等Retrofit注解做处理,以最常用的@Query为例

private ParameterHandler<?> parseParameterAnnotation(int p, Type type, Annotation[] annotations, Annotation annotation) {
    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);
    }
}

首先对type进行校验,其中不能包含泛型和通配符,然后需要创建一个ParameterHandler.Query()对象返回,他需要用到converter对象,converter由retrofit.stringConverter()提供,stringConverter()方法第一个参数是个Type类型,对于Query而言,如果参数type是个Iterable,那么需要获取真实迭代的对象作为参数,如果参数type是个数组,则需要获取数组的类型,对于基本数据类型需要装箱成对应的引用类型。其他类型则无需转换,直接传递。

ParameterHandler

前面使用的ParameterHandler.Query是ParameterHandler的实现类。

abstract class ParameterHandler<T> {
  abstract void apply(RequestBuilder builder, @Nullable T value) throws IOException;
  final ParameterHandler<Iterable<T>> iterable() {}
  final ParameterHandler<Object> array() {}
}

apply()是个抽象方法需要子类实现,ParameterHandler.Query实现如下:

void apply(RequestBuilder builder, @Nullable T value) throws IOException {
  if (value == null) return; 
  String queryValue = valueConverter.convert(value);
  if (queryValue == null) return;
  builder.addQueryParam(name, queryValue, encoded);
}

可以看到apply实际是对请求参数在做处理,将对应的值注入到RequestBuilder中。而iterable()和array()则是对迭代器和数组类型的参数的适配。

创建Request

RequestFactory实例创建之后,便可以调用其create()方法进行创建Request对象。

okhttp3.Request create(Object[] args) throws IOException {
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    int argumentCount = args.length;
    if (argumentCount != handlers.length) {
		//长度不一致则抛出异常
	}
    RequestBuilder requestBuilder =new RequestBuilder(//省略参数...);
	//kotlin挂起函数会在方法最后增加一个Continuation参数,这里需要将它排除掉
    if (isKotlinSuspendFunction) {
      argumentCount--;
    }

    List<Object> argumentList = new ArrayList<>(argumentCount);
    for (int p = 0; p < argumentCount; p++) {
      argumentList.add(args[p]);
      handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();
  }

Object[] args是我们调用ApiService方法传进来的参数,他应该和通过注解解析到的参数长度一致,否则就会抛出异常。然后创建RequestBuilder对象,如果当前方法是kotlin挂起函数,则需要排除最后一个Continuation参数。然后遍历handlers调用apply()方法将参数设置到requestBuilder中。最后调用requestBuilder.build()方法创建Request对象。

猜你喜欢

转载自juejin.im/post/7112099022231322632