当前位置: 首页 > 后端技术 > Java

SpringQueryMap注解被误挖

时间:2023-04-01 16:58:15 Java

简介最近因为业务需要,需要连接阿里云的一个接口。打开文档看这个界面,比起简单的目测,几个小时就能搞定,但接入过程相当坎坷。首先看他给的例子,先下载了阿里云文档推荐的demo,跑了它的例子,替换了几个必要的参数比如秘钥。一般公司会有专职人员对接阿里云这些秘钥,你只需要负责他们的需求即可。但不排除部分企业需要自己对接阿里云。说到这里,我想吐槽一下。连接阿里云时,技术支持群其实就是钉钉,所以如果需要他们的支持,就必须下载钉钉。莫名其妙需要在电脑上安装一个额外的软件。言归正传,下载demo,替换相应的key等参数,然后运行demo,看看是否能正常返回结果。这一步主要是为了保证产品给你的秘钥等参数是否正确。如果接口可以断开,说明参数没有问题,接下来我们就可以开始写业务代码了。接入阿里云双因素认证https://market.aliyun.com/pro...官网下载demo,运行看看。官网给出的例子比较简单粗暴,就是封装了一个Apachehttplcient工具类,代码量很大。我还是习惯性的使用feign来调用,因为feign的代码干净整洁。虽然底层也是通过HttpClient实现的,但是具体实现方式我无所谓。毕竟,业务代码看起来干净整洁。其demo如下:publicstaticvoidmain(String[]args){Stringhost="https://safrvcert.market.alicloudapi.com";字符串路径="/safrv_2meta_id_name/";字符串方法="GET";Stringappcode="你自己的AppCode";Mapheaders=newHashMap();//header中的最终格式(中间的英文空格)是Authorization:APPCODE83359fd73fe94948385f570e3c139105headers.put("Authorization","APPCODE"+appcode);Mapqueries=newHashMap();查询。put("__userId","__userId");查询。把(“客户ID”,“客户ID”);queries.put("identifyNum","identifyNum");queries.put("identifyNumMd5","identifyNumMd5");queries.put("用户名","用户名");queries.put("verifyKey","verifyKey");try{/***重要提示如下:*请从*https://github.com/aliyun/api-gateway-demo-sign-java下载HttpUtils/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java*下载**请参考对应的依赖*https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml*/HttpResponseresponse=HttpUtils.doGet(host,path,method,headers,queries);//错误信息见X-Ca-Error-Message字段System.out。println(response.toString());//获取响应体System.out.println(EntityUtils.toString(response.getEntity()));}catch(Exceptione){e.printStackTrace();}}HttpResponseresponse=HttpUtils.doGet(host,path,method,headers,queries);根据它提供的代码,我们可以看到他使用了一个httpUtils类来实现http请求。我们可以用我们的FeignClient替代品来替代这个httpClient类。代码如下:"/safrv_2meta_id_name/",mmethod=RequestMethod.GET,produces=MediaType.APPLICATION_JSON_UTF8_VALUE)响应verifyIdCardAndNameMap(@RequestParamMapapp,@RequestHeader("Authorization")Stringauthorization);相比之下,下面的HttpClientUtils代码是不是比较简单?根据这个demo确实实现了功能。说实话,我还是不太喜欢用map做参数。如果使用map作为入参,参数都是猜测。可读性和可维护性有点差。我还是习惯性的封装一个javaBean作为实体阿里。其实文档里也提到了,虽然他只讲了数据查询层。接下来我们修改请求参数,改成javaBean。修改后的代码@RequestMapping(value="/safrv_2meta_id_name/",method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON_UTF8_VALUE)ResponseverifyIdCardAndNameDTO(@RequestBodyAliyunVerifyIdCardAndNameReqapp,@RequestBodyAliyunVerifyIdCardAndNameReqapp,@RequestBodyRequestHeader("Authorization")Stringauthorization);请求不成功。根据报错返回的信息,好像没有接收到参数。我们采用的是GET请求的方式,然后将参数传递给实体,所以没有收到。feignClient不支持get方法传递实体类吗?后来通过查资料,发现了一个注解@SpringQueryMap。我们将上面代码中的@RequestBody替换成@SpringQueryMap就完美解决了这个问题。@SpringQueryMap在springcloud2.1.x及以上版本提供了一个新的注解@SpringQueryMap。为什么这个注解可以帮助我们实现。源代码下没有秘密。我们可以翻看feign的源码。应该比较简单。我们可以简单的看一下源码。如果你看源代码,你不知道从哪里开始。从头看到尾肯定不现实。如果你不从头开始,不知道源代码在哪里,有一个很简单的方法。看看用到了哪些地方,尽量在每个地方下断点。我们全局搜索了一下,发现用到的地方主要在QueryMapParameterProcessor类。所以我们可以尝试在这个类下个断点。/***{@linkSpringQueryMap}参数处理器。**@authorAramPeres*@seeAnnotatedParameterProcessor*/publicclassQueryMapParameterProcessor实现AnnotatedParameterProcessor{privatestaticfinalClassANNOTATION=SpringQueryMap.class;@extOverridepublicends类getAnnotationType(){返回注释;}@OverridepublicbooleanprocessArgument(AnnotatedParameterContextcontext,Annotationannotation,Methodmethod){intparamIndex=context.getParameterIndex();MethodMetadata元数据=context.getMethodIndex(MapdeMetadata();)==null){metadata.queryMapIndex(paramIndex);metadata.queryMapEncoded(SpringQueryMap.class.cast(annotation).encoded());}返回真;Load,并且会执行processArgument方法,我们先忽略这个方法,然后我们看Feign真正发起调用的地方,找到SynchronousMethodHandler#invoke方法publicRequestTemplatecreate(Object[]argv){...省略部分代码//metadata.queryMapIndex()是QueryMapParameterProcessor#processArgument方法赋值if(metadata.queryMapIndex()!=null){//添加querymap参数后initialresolve以便它们//优先于任何预定义值//通过下标获取需要特殊处理的对象。这里有个问题,只会处理方法参数的第一个@SpringQueryMap注解。//原因是QueryMapParameterProcessor#processArgument方法只会赋值第一个下标,然后这里只会取第一个下标,所以只会处理第一个@SpringQueryMap注解Objectvalue=argv[metadata.queryMapIndex()];//添加对象Converttomap这里需要注意FieldQueryMapEncoder类默认使用解析参数,所以不会解析父类的参数。如果需要解析父类的参数,我们需要在feign的Config中指定QueryMapEncoder为FieldQueryMapEncoderMapqueryMap=toQueryMap(value);//拼接解析完成的对象就是URL参数template=addQueryMapQueryParameters(queryMap,template);}...省略部分代码}上面代码的逻辑还是比较容易理解的。首先判断是否需要处理querymap通过下标获取需要特殊处理的对象,将对象转化为map(这里有一个坑,默认不会解析父类的字段)拼接追加映射到url通过@SpringQueryM对上面进行总结ap注解实现了get参数传递,但是如果我们需要传递多个@SpringQueryMap注解,怎么实现呢?或者我们自己实现自己的SpringQueryMap,应该怎么实现呢?@SpringQueryMap注解默认不解析父类的参数。如果需要解析父类的参数,需要修改Feign的config#QueryMapEncoder为FieldQueryMapEncoder。如果我们实现一个AnnotatedParameterProcessor,所有默认的PathVariableParameterProcessor、RequestParamParameterProcessor、RequestHeaderParameterProcessor、QueryMapParameterProcessor都将失效,为什么会失败?让我们看一下SpringMvcContract类。所以自定义AnnotatedParameterProcessor需要谨慎。最后,由于自己的无知和知识的匮乏,难免有错误。如果您发现错误,请留言指出给我,我会改正。如果您觉得文章还不错,您的转发、分享、欣赏、点赞、评论就是对我最大的鼓励。感谢您的阅读,非常欢迎并感谢您的关注。