持续创作,加速成长!这是我参与「掘金日新计划 · 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()方法主要做了下面五件事
- 解析方法上的注解
- 检查方法上的注解
- 解析参数获取参数上的注解
- 检查参数上的注解
- 创建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);
}
- 首先检查当前方法HTTP请求方式的注解是否唯一,因为我们一次只能以一种请求方式访问服务器。
- 然后检查注解的参数,一般是URL地址,我们知道,
?
是用来分隔实际的URL和参数,如果URL中存在?
,则动态查询参数不能搭配@Path使用,这时候应该使用@Query。 - 如果有动态查询参数的话,通过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).");
}
}
- APIService方法必须要有一个HTTP方法注解。
- 如果使用了@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对象。