引言

最近因为业务的须要,须要接入下阿里云的一个接口,打开文档看了看这个接口看下来还是比简略的目测个把小时就能够搞定,然而接入的过程还是比拟崎岖的。首先我看了看他给的示例,首先把阿里云文档举荐的demo下载下来,把它的例子跑起来,替换下几个必要的参数比方秘钥啥的。这些秘钥个别公司都会有专职的人员与阿里云去对接,你只有负责管他要就行了。不过也不排除也有得公司须要本人去对接阿里云。说到这里就想吐槽下,对接阿里云的时候技术支持群竟然是钉钉,所以须要他们的反对就必须要下载个钉钉,
电脑上莫名的有须要多装一个软件。扯远了咱们还是回到正题,把它demo下载下来,而后把对应的秘钥等参数替换下,而后运行下demo看看是否可能失常返回后果,做这一步次要是为了保障产品给过去的秘钥等参数是否正确。如果可能掉通接口,那就阐明参数没啥问题的接着咱们就能够着手来写业务代码了。接入阿里云二因素认证https://market.aliyun.com/pro...
把官网的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须要谨慎。

完结

  • 因为本人满腹经纶,难免会有纰漏,如果你发现了谬误的中央,还望留言给我指出来,我会对其加以修改。
  • 如果你感觉文章还不错,你的转发、分享、赞叹、点赞、留言就是对我最大的激励。
  • 感谢您的浏览,非常欢送并感谢您的关注。