Retrofit自定义请求参数注解的实现思路
前言
目前我们的项目中仅使用到GET和POST两种请求方式,对于GET请求,请求的参数会拼接在Url中;对于POST请求来说,我们可以通过Body或表单来提交一些参数信息。
Retrofit中使用方式
先来看看在Retrofit中对于这两种请求的声明方式:
GET请求
@GET("transporter/info") FlowablegetTransporterInfo(@Query("uid")longid);
我们使用@Query注解来声明查询参数,每一个参数都需要用@Query注解标记
POST请求
@POST("transporter/update") FlowablechangBind(@BodyMap params);
在Post请求中,我们通过@Body注解来标记需要传递给服务器的对象
Post请求参数的声明能否更直观
以上两种常规的请求方式很普通,没有什么特别要说明的。
有次团队讨论一个问题,我们所有的请求都是声明在不同的接口中的,如官方示例:
publicinterfaceGitHubService{ @GET("users/{user}/repos") Call>listRepos(@Path("user")Stringuser); }
如果是GET请求还好,通过@Query注解我们可以直观的看到请求的参数,但如果是POST请求的话,我们只能够在上层调用的地方才能看到具体的参数,那么POST请求的参数声明能否像GET请求一样直观呢?
@Field注解
先看代码,关于@Field注解的使用:
@FormUrlEncoded @POST("user/edit") CallupdateUser(@Field("first_name")Stringfirst,@Field("last_name")Stringlast);
使用了@Field注解之后,我们将以表单的形式提交数据(first_name=XXX&last_name=yyy)。
基于约定带来的问题
看上去@Field注解可以满足我们的需求了,但遗憾的是之前我们和API约定了POST请求数据传输的格式为JSON格式,显然我们没有办法使用该注解了
Retrofit参数注解的处理流程
这个时候我想是不是可以模仿@Field注解,自己实现一个注解最后使得参数以JSON的格式传递给API就好了,在此之前我们先来看看Retrofit中对于请求的参数是如何处理的:
ServiceMethod中Builder的构造函数
Builder(Retrofitretrofit,Methodmethod){ 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对象的过程中发生了什么:
//省略了部分代码... publicServiceMethodbuild(){ //1.解析方法上的注解 for(Annotationannotation:methodAnnotations){ parseMethodAnnotation(annotation); } intparameterCount=parameterAnnotationsArray.length; parameterHandlers=newParameterHandler>[parameterCount]; for(intp=0;p(this); }
解析方法上的注解parseMethodAnnotation
if(annotationinstanceofGET){ parseHttpMethodAndPath("GET",((GET)annotation).value(),false); }elseif(annotationinstanceofPOST){ parseHttpMethodAndPath("POST",((POST)annotation).value(),true); }
我省略了大部分的代码,整段的代码其实就是来判断方法注解的类型,然后继续解析方法路径,我们仅关注POST这一分支:
privatevoidparseHttpMethodAndPath(StringhttpMethod,Stringvalue,booleanhasBody){ this.httpMethod=httpMethod; this.hasBody=hasBody; //GettherelativeURLpathandexistingquerystring,ifpresent. //... }
可以看到这条方法调用链其实就是确定httpMethod的值(请求方式:POST),hasBody(是否含有Body体)等信息
创建参数处理器
在循环体中为每一个参数都创建一个ParameterHandler:
privateParameterHandler>parseParameter( intp,TypeparameterType,Annotation[]annotations){ ParameterHandler>result=null; for(Annotationannotation:annotations){ ParameterHandler>annotationAction=parseParameterAnnotation( p,parameterType,annotations,annotation); } //省略部分代码... returnresult; }
可以看到方法内部接着调用了parseParameterAnnotation方法来返回一个参数处理器:
对于@Field注解的处理
elseif(annotationinstanceofField){ Fieldfield=(Field)annotation; Stringname=field.value(); booleanencoded=field.encoded(); gotField=true; Converter,String>converter=retrofit.stringConverter(type,annotations); returnnewParameterHandler.Field<>(name,converter,encoded); }
- 获取注解的值,也就是参数名
- 根据参数类型选取合适的Converter
- 返回一个Field对象,也就是@Field注解的处理器
ParameterHandler.Field
//省略部分代码 staticfinalclassFieldextendsParameterHandler { privatefinalStringname; privatefinalConverter valueConverter; privatefinalbooleanencoded; //构造函数... @Override voidapply(RequestBuilderbuilder,@NullableTvalue)throwsIOException{ StringfieldValue=valueConverter.convert(value); builder.addFormField(name,fieldValue,encoded); } }
通过apply方法将@Filed标记的参数名,参数值添加到了FromBody中
对于@Body注解的处理
elseif(annotationinstanceofBody){ Converter,RequestBody>converter; try{ converter=retrofit.requestBodyConverter(type,annotations,methodAnnotations); }catch(RuntimeExceptione){ //Wideexceptionrangebecausefactoriesareusercode.throwparameterError(e,p,"Unabletocreate@Bodyconverterfor%s",type); } gotBody=true; returnnewParameterHandler.Body<>(converter); }
- 选取合适的Converter
- gotBody标记为true
- 返回一个Body对象,也就是@Body注解的处理器
ParameterHandler.Body
staticfinalclassBodyextendsParameterHandler { privatefinalConverter converter; Body(Converter converter){ this.converter=converter; } @Override voidapply(RequestBuilderbuilder,@NullableTvalue){ RequestBodybody; try{ body=converter.convert(value); }catch(IOExceptione){ thrownewRuntimeException("Unabletoconvert"+value+"toRequestBody",e); } builder.setBody(body); } }
通过Converter将@Body声明的对象转化为RequestBody,然后设置赋值给body对象
apply方法什么时候被调用
我们来看看OkHttpCall的同步请求execute方法:
//省略部分代码... @Override publicResponseexecute()throwsIOException{ okhttp3.Callcall; synchronized(this){ call=rawCall; if(call==null){ try{ call=rawCall=createRawCall(); }catch(IOException|RuntimeException|Errore){throwIfFatal(e);//DonotassignafatalerrortocreationFailure. creationFailure=e; throwe; } } returnparseResponse(call.execute()); }
在方法的内部,我们通过createRawCall方法来创建一个call对象,createRawCall方法内部又调用了serviceMethod.toRequest(args);方法来创建一个Request对象:
/** *根据方法参数创建一个HTTP请求 */ RequesttoRequest(@NullableObject...args)throwsIOException{ RequestBuilderrequestBuilder=newRequestBuilder(httpMethod,baseUrl,relativeUrl,headers,contentType,hasBody,isFormEncoded,isMultipart); ParameterHandler