一个报错引发的追寻之路:

Feign get接口传输对象,调用方接口代码:

  1. @FeignClient(name = "manage")
  2. public interface AccessApiService {
  3. @RequestMapping(value = "/interface/listWithRules", method = RequestMethod.GET)
  4. Result<PageQueryResult<InterfaceInfo>> listWithRules(ApplicationSearchParams params);
  5. }

Feign中定义的一个get接口,参数是一个对象,调用后返回一个 「Request method ‘POST’ not supported」的报错。

追寻开始

断点打进去看,查看feign.Client.Default内部类的execute方法,最终使用了jdk自带的HttpURLConnection

最终可以看到这段代码:

  1. private synchronized OutputStream getOutputStream0() throws IOException {
  2. try {
  3. if(!this.doOutput) {
  4. throw new ProtocolException("cannot write to a URLConnection if doOutput=false - call setDoOutput(true)");
  5. } else {
  6. if(this.method.equals("GET")) {
  7. this.method = "POST";
  8. }

传输对象这种情况,被粗暴的用修改请求method的方式来解决,在http协议里复杂的内容可以放入body。所以就有了上面莫名其妙的报错。我们想,如果不修改请求方式,依然使用get,我们就必须要吧对象中的参数一个个拿出来拼接到url里,试想下这个对象再复杂一些,比如包了其他的对象呢,怎么拼接?此时我们想一个对象直接转json丢body传输的确是个好办法,怪不的上面想这么粗暴的办法。

但是修改请求方式破坏了服务提供放的接口,看起来不是很优雅。找解决方案的时候说使用httpclient代替jdk自带方式就可以解决问题了。

具体如下配置:

1,开启feign的httpclient
  1. feign:
  2. httpclient:
  3. enabled: true
2,引入feign-httpclient,注意版本需要和自己依赖的feign版本保持一致
  1. <dependency>
  2. <groupId>com.netflix.feign</groupId>
  3. <artifactId>feign-httpclient</artifactId>
  4. <version>${feign-httpclient}</version>
  5. </dependency>
3,确认引入httpclient包
  1. <dependency>
  2. <groupId>org.apache.httpcomponents</groupId>
  3. <artifactId>httpclient</artifactId>
  4. <version>4.5.3</version>
  5. </dependency>

如此请求服务就通了,可是会发现对象的参数都是空的,还有一步:

服务提供的接口上需要加上@RequestBody来接参数。对,就是@RequestBody!也就是在body里拿的参数。

  1. Result<PageQueryResult<InterfaceInfo>> listWithRules(@RequestBody ApplicationSearchParams params);

那么问题来了,一个get请求怎么来的body呢?

好吧,http协议只是一个协议,理论上只要你想实现无论用什么请求方式都可以带body,只是我们规范约定body是post请求专用而已。

那么httpclient并不会做这个事,还是feign在调用httpclient时,产生了一个get请求并且带着body。

具体代码在feign.httpclient.ApacheHttpClient#toHttpUriRequest:

  1. if (request.body() != null) {
  2. entity = null;
  3. Object entity;
  4. if (request.charset() != null) {
  5. ContentType contentType = this.getContentType(request);
  6. content = new String(request.body(), request.charset());
  7. entity = new StringEntity(content, contentType);
  8. } else {
  9. entity = new ByteArrayEntity(request.body());
  10. }
  11. requestBuilder.setEntity((HttpEntity)entity);
  12. }

至此,似乎我们只能选择后者来解决了,这种方式似乎也是有点畸形,毕竟都破坏掉http协议规范了。

所以不推荐使用,推荐所有get请求,调用方的接口代码全部写成类似如下:

  1. @RequestMapping(value = "/interface/listWithRules", method = RequestMethod.GET)
  2. Result<PageQueryResult<InterfaceInfo>> listWithRules(
  3. @RequestParam(value = "gatewayName") String gatewayName, @RequestParam(value = "modifiedTime") Integer modifiedTime, @RequestParam(value = "pageIndex") Integer pageIndex, @RequestParam(value = "pageSize") Integer pageSize);

而服务提供方可以使用对象。这里注意每个参数注解RequestParam必须要带有value字段,否则会有这个报错:

「RequestParam.value() was empty on parameter 0」

问题又来了,在spring mvc使用中都是默认参数名字叫什么这个value就叫什么的呀,怎么这里还必须要自己定义一下呢?

你遇到的问题世界上总有人已经遇到过,我觉得这个解答是最好的,其他你都不用再看了:

https://stackoverflow.com/questions/44313482/fiegn-client-with-spring-boot-requestparam-value-was-empty-on-parameter-0/52099007

再展开一下,feign在解析@RequestParam注解时的代码在org.springframework.cloud.netflix.feign.annotation.RequestParamParameterProcessor 如下:

  1. public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
  2. int parameterIndex = context.getParameterIndex();
  3. Class<?> parameterType = method.getParameterTypes()[parameterIndex];
  4. MethodMetadata data = context.getMethodMetadata();
  5. if (Map.class.isAssignableFrom(parameterType)) {
  6. checkState(data.queryMapIndex() == null, "Query map can only be present once.");
  7. data.queryMapIndex(parameterIndex);
  8. return true;
  9. }
  10. RequestParam requestParam = ANNOTATION.cast(annotation);
  11. String name = requestParam.value();
  12. checkState(emptyToNull(name) != null,
  13. "RequestParam.value() was empty on parameter %s",
  14. parameterIndex);
  15. context.setParameterName(name);
  16. Collection<String> query = context.setTemplateParameter(name,
  17. data.template().queries().get(name));
  18. data.template().query(name, query);
  19. return true;
  20. }

因为通过反射又无法拿到方法字段的名字(jdk8以上,可以通过增加编译参数开启这个能力),所以feign就放弃了,而spring 为什么能做到获取到方法字段名称呢?

因为它有一套使用asm为基础解系class文件的能力,就是直接打开class看,那还有什么查不到的哦。具体类:LocalVariableTableParameterNameDiscoverer 这个类已经关系的asm了。

追寻结束

到这里一场追寻告一段落,会想一下也很简单,为了解决get请求传输复杂对象的情况,一个http请求必然会面临这个问题,但是话说回来,一个get接口规范上不应该会定义什么复杂对象,而是几个参数而已,如果需呀很复杂的对象才能完成这个get接口,可能需呀思考这个接口设计的合理性了。

Feign get接口传输对象引发一场追寻的更多相关文章

  1. feign调用接口session丢失解决方案

    微服务使用feign相互之间调用时,因为feign默认不传输Header,存在session丢失的问题.例如,使用Feign调用某个远程API,这个远程API需要传递一个鉴权信息,我们可以把cooki ...

  2. Mina使用总结(四)传输对象ObjectSerializationCodecFactory

    用mina框架传输对象,对于开发者来说,直接传输对象,而不用自己编写相应的报文转换代码,将大大节省 开发时间. 即使用对象编码解码器 使用ObjectSerializationCodecFactory ...

  3. Java Socket实战之三:传输对象

    转自:https://i.cnblogs.com/EditPosts.aspx?opt=1 前面两篇文章介绍了怎样建立Java Socket通信,这一篇说一下怎样使用Java Socket来传输对象. ...

  4. Swift是一个提供RESTful HTTP接口的对象存储系统

    Swift是一个提供RESTful HTTP接口的对象存储系统,最初起源于Rackspace的Cloud Files,目的是为了提供一个和AWS S3竞争的服务. Swift于2010年开源,是Ope ...

  5. 探讨 java中 接口和对象的关系

    接口是对象么?接口可以有对象么?这个问题要跟类比对着,或许更好理解;类是对象的模版.接口不是类,所以:接口肯定不是对象的模版.那接口跟对象有什么样的关系?还是得从类入手;因为类实现了接口,所以可以说, ...

  6. Effective Java 第三版——64. 通过对象的接口引用对象

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  7. 实现现下列哪一种接口的对象,并不需要在web.xml文件内进行额外的设定,Servlet容器就能够回应该对象加入HTTP会话所发生的事件?(选择1项)

    实现现下列哪一种接口的对象,并不需要在web.xml文件内进行额外的设定,Servlet容器就能够回应该对象加入HTTP会话所发生的事件?(选择1项) A.ServletContextListener ...

  8. Netty--JDK序列化编解码传输对象

    使用JDK序列化不需要额外的类库,只需要实现Serializable即可,但是序列化之后的码流只有Java才能反序列化,所以它不是跨语言的,另外由于Java序列化后码流比较大,效率也不高,所以在RPC ...

  9. ActiveMQ服务器之间传输对象,项目A发送对象到项目B接收发送对象《二》

    ActiveMQ服务器之间传输对象,项目A发送对象到项目B接收发送对象<一> 上一篇文章写到对象之间传输使用线程方式 ,无法使用监听方式,最近解决了使用监听方式接收对象,本次使用配置文件方 ...

随机推荐

  1. gevent模块学习(四)

    gevent.spawn会对传入的子任务集合进行调度,gevent.joinall 方法会阻塞当前程序,除非所有的greenlet都执行完毕,才会退出程序 公有方法 gevent.spawn(cls, ...

  2. STL 小白学习(10) map

    map的构造函数 map<int, string> mapS; 数据的插入:用insert函数插入pair数据,下面举例说明 mapStudent.insert(pair<, &qu ...

  3. 面试北京XX科技总结

    1             面试时间与地点 面试时间:2019年1月17号,面试地点:北京. 2             公司概况 开发的产品是集团内部使用,开发的语言ts脚本语言.目前开发团队15人 ...

  4. Loadrunner录制https脚本

        随着公司的发展,公司原有的SVN服务器存放的内容不断增加,容量已经不能满足后续需求,首先我们想到对服务器进行扩容,然而因为各种原因服务器不能进行扩容,所以公司决定更换新的SVN服务器,在做数据 ...

  5. canal-client无法获取数据

    在虚拟机单cpu环境下 canal.properties配置中 #canal.instance.parser.parallelThreadSize = 16 那么,MysqlMultiStageCop ...

  6. 指导手册06:HBase安装部署

    指导手册06:HBase安装部署 配置环境 1.参考文件: https://www.cnblogs.com/lzxlfly/p/7221890.html https://www.cnblogs.com ...

  7. MyBatis-day1

    Tips: 1, SQLSession通过SQLSessionFactory获得, SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得.有两种配 ...

  8. ecplise多个版本tomcat的使用

    在ecplise中修改配置文件,或者直接在server.xml中修改,将tomcat三个端口号修改为与另一个tomcat不同即可.

  9. jquery添加属性使用attr、prop。

    之前页面为标签添加属性都是使用的attr,删除使用removeAttr. 今天给checkbox添加checked属性时出现代码显示添加成功,但是页面不勾选内容. 后来查询发现checked是chec ...

  10. Python 12306登陆详细分析及操作

    前面的话: 1.第一次尝试爬虫,登陆12306,有不足的地方,望大家留言告知,谢谢. 2.前面引入了一个requests模块,我不多说,大家都知道干啥的.还有config是我的一个配置文件,因为其中涉 ...