feign的一个注解居然还隐藏这么多知识

引言

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

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容