引言

最近由于业务的需要,需要接入下阿里云的一个接口,打开文档看了看这个接口看下来还是比简单的目测个把小时就可以搞定,但是接入的过程还是比较坎坷的。首先我看了看他给的示例,首先把阿里云文档推荐的demo下载下来,把它的例子跑起来,替换下几个必要的参数比如秘钥啥的。这些秘钥一般公司都会有专职的人员与阿里云去对接,你只要负责管他要就行了。不过也不排除也有得公司需要自己去对接阿里云。说到这里就想吐槽下,对接阿里云的时候技术支持群居然是钉钉,所以需要他们的支持就必须要下载个钉钉,

电脑上莫名的有需要多装一个软件。扯远了我们还是回到正题,把它demo下载下来,然后把对应的秘钥等参数替换下,然后运行下demo看看是否能够正常返回结果,做这一步主要是为了保证产品给过来的秘钥等参数是否正确。如果能够掉通接口,那就说明参数没啥问题的接着我们就可以着手来写业务代码了。接入阿里云二要素认证https://market.aliyun.com/products/57000002/cmapi029454.html?spm=5176.10695662.1194487.1.60066c190NsSkZ#sku=yuncode2345400003

把官网的demo下载下来跑起来看看,官网给出的例子还是比较简单粗暴的,就是封装了一个Apachehttplcient工具类一大坨的代码,个人还是习惯性的使用feign来进行调用,因为feign的代码干净整洁,虽然底层也是通过HttpClient来实现,但是实现对我来说是无感的,毕竟业务代码看起来干净整洁。它的demo如下:

public static void main(String[] args) {
String host = "https://safrvcert.market.alicloudapi.com";
String path = "/safrv_2meta_id_name/";
String method = "GET";
String appcode = "你自己的AppCode";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
Map<String, String> querys = new HashMap<String, String>();
querys.put("__userId", "__userId");
querys.put("customerID", "customerID");
querys.put("identifyNum", "identifyNum");
querys.put("identifyNumMd5", "identifyNumMd5");
querys.put("userName", "userName");
querys.put("verifyKey", "verifyKey"); try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/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
*/
HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);
//错误信息见X-Ca-Error-Message字段
System.out.println(response.toString());
//获取response的body
System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);

根据它提供的代码我们可以看出来他是用一个httpUtils 类来实现http请求的,我们可以把这个httpClient类 替换成我们的FeignClient

替换后的代码如下:

@FeignClient(name = "verifyIdCardAndNameFeignClient", url = "https://safrvcert.market.alicloudapi.com")
public interface VerifyIdCardAndNameFeignClient {
@RequestMapping(value = "/safrv_2meta_id_name/", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
Response verifyIdCardAndNameMap(@RequestParam Map<String,String> app, @RequestHeader("Authorization") String authorization);

相对比较下来下面这个HttpClientUtils代码是不是比较简洁



按照这个demo功能确实是实现了,说实话个人还是不是很喜欢用map来作为参数,map作为入参的话,参数全靠猜可读性以及可维护性有点差,个人还是习惯性的封装一个javaBean作为实体。阿里文档其实也有提到一嘴,虽然他只说到数据查询这一层。



下面我们就修改下请求参数把它改成一个javaBean,改变后的代码

@RequestMapping(value = "/safrv_2meta_id_name/", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
Response verifyIdCardAndNameDTO(@RequestBody AliyunVerifyIdCardAndNameReq app, @RequestHeader("Authorization") String authorization);



请求并没有成功,根据报错返回的信息看下来应该是没有接受到参数。我们是GET请求的方式然后参数传递的是实体导致没有接收到。feignClient不支持get方式传递实体类吗?后来经过查询资料发现了一个注解@SpringQueryMap 我们把上述代码@RequestBody替换成@SpringQueryMap完美解决这个问题

@SpringQueryMap

spring cloud 2.1.x 以上的版本,提供了一个新的注解@SpringQueryMap,为何这个注解可以帮我们实现。源码之下无秘密,我们可以翻翻

feign的源码相对来说应该是比较简单的,我们可以简单的来看下源码。看源码是不是也不知道从哪里看起,从头看到尾肯定也不现实,

不从头开始看,又不知道源码在哪里,有个很简单的方法我们直接拿着这个注解全局搜一下,看看有哪些地方使用到了,在每个地方都打上一个断点试试



我们全局搜下发现使用的地方主要在QueryMapParameterProcessor这个类里面。所以我们可以在这个类里面打上一个断点试试。


/**
* {@link SpringQueryMap} parameter processor.
*
* @author Aram Peres
* @see AnnotatedParameterProcessor
*/
public class QueryMapParameterProcessor implements AnnotatedParameterProcessor { private static final Class<SpringQueryMap> ANNOTATION = SpringQueryMap.class; @Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
} @Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int paramIndex = context.getParameterIndex();
MethodMetadata metadata = context.getMethodMetadata();
if (metadata.queryMapIndex() == null) {
metadata.queryMapIndex(paramIndex);
metadata.queryMapEncoded(SpringQueryMap.class.cast(annotation).encoded());
}
return true;
}
}

我们发现打这个类的话在容器启动的时候会进行加载,并且会执行processArgument方法,这个我们先不管这个方法,接下来我们来看看

Feign真正发起调用的地方找到SynchronousMethodHandler#invoke方法

public RequestTemplate create(Object[] argv) {
... 省略部分代码
// metadata.queryMapIndex() 就是QueryMapParameterProcessor #processArgument方法赋值的
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
// 通过下标获取到需要特殊处理的对象,这里有个问题只会处理方法参数的第一个@SpringQueryMap注解,
// 原因就是QueryMapParameterProcessor #processArgument这个方法只会把第一个下标赋值进去,然后这里也只会取第一个下标,所以只会处理第一个@SpringQueryMap注解
Object value = argv[metadata.queryMapIndex()];
//将对象转换为map 这里需要注意下默认使用解析参数的是FieldQueryMapEncoder类所以它并不会去解析父类的参数,如果需要解析父类的参数我们需要在feign的Config里面指定QueryMapEncoder为FieldQueryMapEncoder
Map<String, Object> queryMap = toQueryMap(value);
//拼接解析完成的对象为URL参数
template = addQueryMapQueryParameters(queryMap, template);
}
... 省略部分代码
}

上述代码逻辑还是挺好理解的

  • 首先去判断是否需要处理下querymap
  • 通过下标获取到需要特殊处理的对象
  • 将对象转换为map(这里有个坑默认不会去解析父类的字段)
  • 拼接追加mapurl

总结

  • 上面通过@SpringQueryMap注解实现了get传参,但是如果需要传递多个@SpringQueryMap注解我们可以怎么来实现呢?
  • 或者我们可以自己动手来实现一个我们自己的SpringQueryMap,我们该如何实现?
  • @SpringQueryMap注解默认是不会去解析父类的参数,如果需要解析父类的参数需要修改Feignconfig# QueryMapEncoderFieldQueryMapEncoder
  • 如果我们自己去实现了一个AnnotatedParameterProcessor所有默认的PathVariableParameterProcessor

    RequestParamParameterProcessor、RequestHeaderParameterProcessor、QueryMapParameterProcessor都会失效,为啥会失效我们去看看SpringMvcContract这个类。所以自定义AnnotatedParameterProcessor需要慎重。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。

feign的一个注解居然隐藏这么多知识!的更多相关文章

  1. SpringBoot整合定时任务----Scheduled注解实现(一个注解全解决)

    一.使用场景 定时任务在开发中还是比较常见的,比如:定时发送邮件,定时发送信息,定时更新资源,定时更新数据等等... 二.准备工作 在Spring Boot程序中不需要引入其他Maven依赖 (因为s ...

  2. 运用Spring Aop,一个注解实现日志记录

    运用Spring Aop,一个注解实现日志记录 1. 介绍 我们都知道Spring框架的两大特性分别是 IOC (控制反转)和 AOP (面向切面),这个是每一个Spring学习视频里面一开始都会提到 ...

  3. 一个注解解决ShardingJdbc不支持复杂SQL

    背景介绍 公司最近做分库分表业务,接入了 Sharding JDBC,接入完成后,回归测试时发现好几个 SQL 执行报错,关键这几个表都还不是分片表.报错如下: 这下糟了嘛.熟悉 Sharding J ...

  4. 一个诡异的MySQL查询超时问题,居然隐藏着存在了两年的BUG

    这一周线上碰到一个诡异的BUG. 线上有个定时任务,这个任务需要查询一个表几天范围内的一些数据做一些处理,每隔十分钟执行一次,直至成功. 通过日志发现,从凌晨5:26分开始到5:56任务执行了三次,三 ...

  5. spring cloud Feign 使用 @RequestLine 注解遇到的问题

    package com.itmuch.cloud; import org.springframework.cloud.netflix.feign.FeignClient; import com.itm ...

  6. Feign通过自定义注解实现路径的转义

    本文主要讲解如果通过注解实现对路由中的路径进行自定义编码 背景 近期由于项目中需要,所以需要通过Feign封装一个对Harbor操作的sdk信息. 在调用的过程中发现,当请求参数中带有"/& ...

  7. 【hibernate】1、Hibernate的一个注解 @Transient

    @Transient表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性.如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic ...

  8. 一个注解搞懂 Sentinel,@SentinelResource 总结

    在前面的博客中,我给大家演示了使用 @SentinelResource 定义资源完成限流的例子, 下面就从源码解析开始,看下SentinelResource是如何实现限流的,以及@SentinelRe ...

  9. Spring Security基于Oauth2的SSO单点登录怎样做?一个注解搞定

    一.说明 单点登录顾名思义就是在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统,免除多次登录的烦恼.本文主要介绍 同域 和 跨域 两种不同场景单点登录的实现原理,并使用 Spring ...

随机推荐

  1. 如何让BootStrap栅格之间留出空白间隙呢?

    BootStrap栅格之间留出空隙 BootStrap栅格系统可以把我们的container容器划分为若干等分,如果想要每个部分之间留出一定的空隙,我们很可能首先想到的方法就是用margin外边距来使 ...

  2. 第一章 Net 5.0 快速开发框架 YC.Boilerplate--框架介绍

    YC.Boilerplate 框架介绍 YC.Boilerplate 是一套快速开发框架,采用当下流行的前后端分离开发模式,前端 采用VUE.后端采用Net 5.0:框架实现了 多租户.动态webAp ...

  3. 经典深度学习CNN总结 - LeNet、AlexNet、GoogLeNet、VGG、ResNet

    参考了: https://www.cnblogs.com/52machinelearning/p/5821591.html https://blog.csdn.net/qq_24695385/arti ...

  4. Notepad++插件推荐和下载

    Notepad++因为其强劲的插件支持,越来越受到编程爱好者的喜欢.很多优秀的插件现在已经默认安装了,下面是100多款受欢迎的Notepad++插件的介绍和下载地址. XML Tools 这个插件是包 ...

  5. 基于Linux系统Samba服务器的部署

    1.基础信息 用 Internet 文件系统 CIFS(Common Internet File System)是适用于MicrosoftWindows 服务器和客户端的标准文件和打印机共享系统信息块 ...

  6. vue引用 element-ui 的 el-aside -> el-menu -> el-menu-item 路由侧边栏跳转链接,接上动态路由

    npm 下载 npm i element-ui -S Vue main.js //引入element-ui的包 import ElementUI from 'element-ui';   //引入el ...

  7. 模拟9:T1:斐波那契

    Description: 题目描述:   小 C 养了一些很可爱的兔子.   有一天,小 C 突然发现兔子们都是严格按照伟大的数学家斐波那契提出的模型来进行繁衍:一对兔子从出生后第二个月起,每个月刚开 ...

  8. linux清空文件

    https://www.cnblogs.com/mrwang1101/p/6166326.html

  9. python库--pandas--写入文本文件

    pandas.DataFrame.to_csv 参数 说明 path_or_buf=None 文件路径或对象, 若果为None, 则结果作为字符串返回 sep=',' 分隔符 na_rep='' 缺失 ...

  10. paramido简单使用教程

    之前很少用paramilko  今天想安装一下使用,然后百度了一下,然后就是各种 奇云技巧 ,各大博客照 着做都难成功,,然后火了,直接在CMD下输入 pip install paramiko     ...