乐趣区

关于java:SpringQueryMap-这个注解一不小心就采坑了

引言

最近因为业务的须要,须要接入下阿里云的一个接口,打开文档看了看这个接口看下来还是比简略的目测个把小时就能够搞定,然而接入的过程还是比拟崎岖的。首先我看了看他给的示例,首先把阿里云文档举荐的 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、QueryMapParameterProcesso r 都会生效,为啥会生效咱们去看看SpringMvcContract 这个类。所以自定义 AnnotatedParameterProcessor 须要谨慎。

完结

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