欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Retrofit自定义请求参数注解的实现思路

程序员文章站 2023-02-07 15:38:34
前言 目前我们的项目中仅使用到 get 和 post 两种请求方式,对于 get 请求,请求的参数会拼接在 url 中;对于 post 请求来说,我们可以通过 body...

前言

目前我们的项目中仅使用到 get 和 post 两种请求方式,对于 get 请求,请求的参数会拼接在 url 中;对于 post 请求来说,我们可以通过 body 或表单来提交一些参数信息。

retrofit 中使用方式

先来看看在 retrofit 中对于这两种请求的声明方式:

get 请求

@get("transporter/info")
flowable<transporter> gettransporterinfo(@query("uid") long id);

我们使用 @query 注解来声明查询参数,每一个参数都需要用 @query 注解标记

post 请求

@post("transporter/update")
flowable<responsebody> changbind(@body map<string,object> params);

在 post 请求中,我们通过 @body 注解来标记需要传递给服务器的对象

post 请求参数的声明能否更直观

以上两种常规的请求方式很普通,没有什么特别要说明的。

有次团队讨论一个问题,我们所有的请求都是声明在不同的接口中的,如官方示例:

public interface githubservice {
 @get("users/{user}/repos")
 call<list<repo>> listrepos(@path("user") string user);
}

如果是 get 请求还好,通过 @query 注解我们可以直观的看到请求的参数,但如果是 post 请求的话,我们只能够在上层调用的地方才能看到具体的参数,那么 post 请求的参数声明能否像 get 请求一样直观呢?

@field 注解

先看代码,关于 @field 注解的使用:

@formurlencoded
@post("user/edit")
call<user> updateuser(@field("first_name") string first, @field("last_name") string last);

使用了 @field 注解之后,我们将以表单的形式提交数据(first_name = xxx & last_name = yyy)。

基于约定带来的问题

看上去 @field 注解可以满足我们的需求了,但遗憾的是之前我们和 api 约定了 post 请求数据传输的格式为 json 格式,显然我们没有办法使用该注解了

retrofit 参数注解的处理流程

这个时候我想是不是可以模仿 @field 注解,自己实现一个注解最后使得参数以 json 的格式传递给 api 就好了,在此之前我们先来看看 retrofit 中对于请求的参数是如何处理的:

servicemethod 中 builder 的构造函数

builder(retrofit retrofit, method method) {
 this.retrofit = retrofit;
 this.method = method;
 this.methodannotations = method.getannotations();
 this.parametertypes = method.getgenericparametertypes();
 this.parameterannotationsarray = method.getparameterannotations();
}

我们关注三个属性:

  • methodannotations 方法上的注解,annotation[] 类型
  • parametertypes 参数类型,type[] 类型
  • parameterannotationsarray 参数注解,annotation[][] 类型

在构造函数中,我们主要对这 5 个属性赋值。

builder 构造者的 build 方法

接着我们看看在通过 build 方法创建一个 servicemethod 对象的过程中发生了什么:

//省略了部分代码...

public servicemethod build() {
 //1. 解析方法上的注解
 for (annotation annotation : methodannotations) {
 parsemethodannotation(annotation);
 }

 int parametercount = parameterannotationsarray.length;
 parameterhandlers = new parameterhandler<?>[parametercount];
 for (int p = 0; p < parametercount; p++) {
 type parametertype = parametertypes[p];

 annotation[] parameterannotations = parameterannotationsarray[p];
 //2. 通过循环为每一个参数创建一个参数处理器
 parameterhandlers[p] = parseparameter(p, parametertype, parameterannotations);
 }
 return new servicemethod<>(this);
}

解析方法上的注解 parsemethodannotation

if (annotation instanceof get) {
 parsehttpmethodandpath("get", ((get) annotation).value(), false);
}else if (annotation instanceof post) {
 parsehttpmethodandpath("post", ((post) annotation).value(), true);
} 

我省略了大部分的代码,整段的代码其实就是来判断方法注解的类型,然后继续解析方法路径,我们仅关注 post 这一分支:

private void parsehttpmethodandpath(string httpmethod, string value, boolean hasbody) {
 this.httpmethod = httpmethod;
 this.hasbody = hasbody;
 // get the relative url path and existing query string, if present.
 // ...
}

可以看到这条方法调用链其实就是确定 httpmethod 的值(请求方式:post),hasbody(是否含有 body 体)等信息

创建参数处理器

在循环体中为每一个参数都创建一个 parameterhandler:

private parameterhandler<?> parseparameter(
 int p, type parametertype, annotation[] annotations) {
 parameterhandler<?> result = null;
 for (annotation annotation : annotations) {
 parameterhandler<?> annotationaction = parseparameterannotation(
 p, parametertype, annotations, annotation);
 }
 // 省略部分代码...
 return result;
}

可以看到方法内部接着调用了 parseparameterannotation 方法来返回一个参数处理器:

对于 @field 注解的处理

else if (annotation instanceof field) {
 field field = (field) annotation;
 string name = field.value();
 boolean encoded = field.encoded();

 gotfield = true;
 converter<?, string> converter = retrofit.stringconverter(type, annotations);
 return new parameterhandler.field<>(name, converter, encoded);

}
  • 获取注解的值,也就是参数名
  • 根据参数类型选取合适的 converter
  • 返回一个 field 对象,也就是 @field 注解的处理器

parameterhandler.field

//省略部分代码
static final class field<t> extends parameterhandler<t> {
 private final string name;
 private final converter<t, string> valueconverter;
 private final boolean encoded;

 //构造函数...

 @override
 void apply(requestbuilder builder, @nullable t value) throws ioexception {
 string fieldvalue = valueconverter.convert(value);
 builder.addformfield(name, fieldvalue, encoded);
 }
}

通过 apply 方法将 @filed 标记的参数名,参数值添加到了 frombody 中

对于 @body 注解的处理

else if (annotation instanceof body) {
 converter<?, requestbody> converter;
 try {
 converter = retrofit.requestbodyconverter(type, annotations, methodannotations);
 } catch (runtimeexception e) {
 // wide exception range because factories are user code.throw parametererror(e, p, "unable to create @body converter for %s", type);
 }
 gotbody = true;
 return new parameterhandler.body<>(converter);
}
  • 选取合适的 converter
  • gotbody 标记为 true
  • 返回一个 body 对象,也就是 @body 注解的处理器

parameterhandler.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, @nullable t value) {
 requestbody body;
 try {
 body = converter.convert(value);
 } catch (ioexception e) {
 throw new runtimeexception("unable to convert " + value + " to requestbody", e);
 }
 builder.setbody(body);
 }
}

通过 converter 将 @body 声明的对象转化为 requestbody,然后设置赋值给 body 对象

apply 方法什么时候被调用

我们来看看 okhttpcall 的同步请求 execute 方法:

//省略部分代码...
@override
public response<t> execute() throws ioexception {
 okhttp3.call call;

 synchronized (this) {
 call = rawcall;
 if (call == null) {
 try {
 call = rawcall = createrawcall();
 } catch (ioexception | runtimeexception | error e) { throwiffatal(e); // do not assign a fatal error to creationfailure.
 creationfailure = e;
  throw e;
 }
 }
 return parseresponse(call.execute());
}

在方法的内部,我们通过 createrawcall 方法来创建一个 call 对象,createrawcall 方法内部又调用了 servicemethod.torequest(args);方法来创建一个 request 对象:

/**
 * 根据方法参数创建一个 http 请求
 */
request torequest(@nullable object... args) throws ioexception {
 requestbuilder requestbuilder = new requestbuilder(httpmethod, baseurl, relativeurl, headers, contenttype, hasbody, isformencoded, ismultipart);
 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();
}

可以看到在 for 循环中执行了每个参数对应的参数处理器的 apply 方法,给 requestbuilder 中相应的属性赋值,最后通过 build 方法来构造一个 request 对象,在 build 方法中还有至关重要的一步:就是确认我们最终的 body 对象的来源,是来自于 @body 注解声明的对象还是来自于其他

requestbody body = this.body;
if (body == null) {
 // try to pull from one of the builders.
 if (formbuilder != null) {
 body = formbuilder.build();
 } else if (multipartbuilder != null) {
 body = multipartbuilder.build();
 } else if (hasbody) {
 // body is absent, make an empty body.
 body = requestbody.create(null, new byte[0]);
 }
}

自定义 post 请求的参数注解 @bodyquery

根据上述流程,想要自定义一个参数注解的话,涉及到以下改动点:

  • 新增类 @bodyquery 参数注解
  • 新增类 bodyquery 用来处理 @bodyquery 声明的参数
  • servicemethod 中的 parseparameterannotation 方法新增对 @bodyquery 的处理分支
  • requestbuilder 类,新增 boolean 值 hasbodyquery,表示是否使用了 @bodyquery 注解,以及一个 map 对象 hasbodyquery,用来存储 @bodyquery 标记的参数

@bodyquery 注解

public @interface bodyquery {
 /**
 * the query parameter name.
 */
 string value();

 /**
 * specifies whether the parameter {@linkplain #value() name} and value are already url encoded.
 */
 boolean encoded() default false;
}

没有什么特殊的,copy 的 @query 注解的代码

bodyquery 注解处理器

static final class bodyquery<t> extends parameterhandler<t> {
 private final string name;
 private final converter<t, string> valueconverter;

 bodyquery(string name, converter<t, string> valueconverter) {
 this.name = checknotnull(name, "name == null");
 this.valueconverter = valueconverter;
 }

 @override
 void apply(requestbuilder builder, @nullable t value) throws ioexception {
 string fieldvalue = valueconverter.convert(value);
 builder.addbodyqueryparams(name, fieldvalue);
 }
}

在 apply 方法中我们做了两件事

  • 模仿 field 的处理,获取到 @bodyquery 标记的参数值
  • 将键值对添加到一个 map 中
// 在 requestbuilder 中新增的方法
void addbodyqueryparams(string name, string value) {
 bodyquerymaps.put(name, value);
}

针对 @bodyquery 新增的分支处理

else if (annotation instanceof bodyquery) {
 bodyquery field = (bodyquery) annotation;
 string name = field.value();
 hasbodyquery = true;

 converter<?, string> converter = retrofit.stringconverter(type, annotations);
 return new parameterhandler.bodyquery<>(name, converter);
}

我省略对于参数化类型的判断,可以看到这里的处理和对于 @field 的分支处理基本一致,只不过是返回的 parameterhandler 对象类型不同而已

requestbuilder

之前我们说过在 requestbuilder#build() 方法中最重要的一点是确定 body 的值是来自于 @body 还是表单还是其他对象,这里需要新增一种来源,也就是我们的 @bodyquery 注解声明的参数值:

requestbody body = this.body;
if (body == null) {
 // try to pull from one of the builders.
 if (formbuilder != null) {
 body = formbuilder.build();
 } else if (multipartbuilder != null) {
 body = multipartbuilder.build();
 } else if (hasbodyquery) {
 body = requestbody.create(mediatype.parse("application/json; charset=utf-8"), json.tojsonbytes(this.bodyquerymaps));

 } else if (hasbody) {
 // body is absent, make an empty body.
 body = requestbody.create(null, new byte[0]);
 }
}

在 hasbodyquery 的分支,我们会将 bodyquerymaps 转换为 json 字符串然后构造一个 requestbody 对象赋值给 body。

最后

通过一个例子来看一下 @bodyquery 注解的使用:

@test
public void simplebodyquery(){
 class example{
 @post("/foo")
 call<responsebody> method(@bodyquery("a") string foo,@bodyquery("b") string ping){
  return null;
 }
 }
 request request = buildrequest(example.class,"hello","world");
 assertbody(request.body(), "{\"a\":\"hello\",\"b\":\"world\"}");
}

由于 retrofit 中并没有提供这些类的修改和扩展的权限,因此这里仅仅是一个思路的扩展,我也仅仅是顺着 retrofit 中对于 parameterhandler 的处理,扩展了一套新的注解类型而已。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。