引言

最近由于业务的需要,需要接入下阿里云的一个接口,打开文档看了看这个接口看下来还是比简单的目测个把小时就可以搞定,但是接入的过程还是比较坎坷的。首先我看了看他给的示例,首先把阿里云文档推荐的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. java 日期格式化-- SimpleDateFormat 的使用。字符串转日期,日期转字符串

    日期和时间格式由 日期和时间模式字符串 指定.在 日期和时间模式字符串 中,未加引号的字母 'A' 到 'Z' 和 'a' 到 'z' 被解释为模式字母,用来表示日期或时间字符串元素.文本可以使用单引 ...

  2. Python3-sqlalchemy-orm 回滚

    #-*-coding:utf-8-*- #__author__ = "logan.xu" import sqlalchemy from sqlalchemy import crea ...

  3. k8s笔记0528-基于KUBERNETES构建企业容器云手动部署集群记录-2

    三.ETCD集群部署 类似于走zookeeper集群分布式协调服务,可做以key v形式存储在ETCD中. 官方链接:https://github.com/coreos/etcd 分布式kv存储,为分 ...

  4. BUUCTF-[CISCN2019 华东北赛区]Web2

    BUUCTF-[CISCN2019 华东北赛区]Web2 看题 一个论坛,内容不错:) 可以投稿,点击投稿发现要注册,那就先注册登录.随便账号密码就行. 常规操作,扫一下站点,发现有admin.php ...

  5. vue-父子组件之传值和单项数据流问题

    前言 我们知道 vue 中父子组件的核心概念是单项数据流问题,props 是单项传递的.那究竟什么是单项数据流问题,这篇文章来总结一下关于这个知识点的学习笔记. 正文 1.父组件传值给子组件 < ...

  6. JS中原型与原型链

    一. 普通对象与函数对象 JavaScript 中,万物皆对象!但对象也是有区别的.分为普通对象和函数对象,Object .Function等 是 JS 自带的函数对象.下面举例说明. var o1 ...

  7. 使用 IDEA 配合 Dockerfile 部署 SpringBoot 工程

    准备 SpringBoot 工程 新建 SpringBoot 项目,默认的端口是 8080 ,新建 Controller 和 Mapping @RestController public class ...

  8. Dubbo(一)——

    https://www.cnblogs.com/ideal-20/p/14095919.html#_label24 https://segmentfault.com/a/119000001989672 ...

  9. 羽夏笔记——Win32(非WinAPI)

    写在前面   本笔记是由本人独自整理出来的,图片来源于网络.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你 ...

  10. Linux学习笔记--终端命令

    ~ 表示用户目录路径 ls   显示当前目录下的文件或目录 -l 列出文件纤细信息l(list) -a 列出当前目录下所有文件及目录, 包含隐藏的a(all) mkdir   创建目录 -p 创建目录 ...