前言
spring cloud技术栈里面,Feign可以使得我们的rest调用和调用本地方法一样方便。但是它真的有非常多的坑,苦不堪言啊。本文将描述我们最为常遇到的坑:
Feign发送Get请求时,采用POJO传递参数 Request method ‘POST’ not supported
坑 例举
Feign发送Get请求时,采用POJO传递参数的坑
在使用Feign client来调用Get请求接口时,如果方法的参数是一个对象,例如:
@FeignClient("microservice-provider-user") public interface UserFeignClient { @RequestMapping(value = "/user", method = RequestMethod.GET) public PageBean<User> get(User user); }
我们想得好好的。分页查询,查询条件用POJO的User对象进行包装进去。但奈何:在调试的时候你会一脸懵逼,因为报了如下错误:
feign.FeignException: status 405 reading UserFeignClient#get0(User); content: {"timestamp":1482676142940,"status":405,"error":"Method Not Allowed", "exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'POST' not supported","path":"/user"}
what?老夫明明用的get请求啊,你竟然说Post方法不支持?
其实这个问题,在feign的github社区里面,一直有人提出了issue,只是一直没有被解决而已。
github上相关issue参考:
1.希望Feign能够支持参数请求使用POJO:https://github.com/spring-cloud/spring-cloud-netflix/issues/1253
2.解决办法:http://www.itmuch.com/spring-cloud-sum/feign-multiple-params/
3.建议使用Feign原生的注解的Issue:https://github.com/spring-cloud/spring-cloud-netflix/issues/659
4.建议增强Feign的功能:https://github.com/spring-cloud/spring-cloud-netflix/issues/1360
5.建议支持可选的Request Body(目前Feign当POST一个null时,会报异常):https://github.com/spring-cloud/spring-cloud-netflix/issues/1047
虽然可以采用@RequestParam的方式解决问题,但是很恼火的我,仔细想想:
你想写一堆长长的参数吗?用一个不知道里边有什么鬼的Map吗?或者转换为post?这似乎与REST风格不太搭,会浪费url资源,我们还需要在url定义上来区分Get或者Post。
于是就开始逐行调试,知道我从feign的源码中发现了这个:
sun.net.www.protocol.http; public class HttpURLConnection extends java.net.HttpURLConnection { // 这里很简单:只要你doOutput = true了,你是get请求的话也会强制给你转为POST请求 // 这倒不能说是JDK的bug,但是显然我不太喜欢这种处理方式~~~ 你发现是get最多把我的body忽略就可以了呗 private synchronized OutputStream getOutputStream0() throws IOException { try { if (!this.doOutput) { throw new ProtocolException("cannot write to a URLConnection if doOutput=false - call setDoOutput(true)"); } else { if (this.method.equals("GET")) { this.method = "POST"; } } ... } }
这段代码是在 HttpURLConnection 中发现的,jdk原生的http连接请求工具类,原来是因为Feign默认使用的连接工具实现类,所以里面发现只要你有body体对象,就会强制的把get请求转换成POST请求。
终上所述,这也不能怪feign,是HttpURLConnection 的问题。所以接下来我准备换一个HttpClient试试,因此本利我采用apache的HttpClient。但是一定,一定需要加入如下几个步骤:
1.加入feign的配置项:feign.httpclient,enabled = true
2.在依赖中引入apache的httpclient
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency>
3.配置上此依赖(此依赖不可少 否则不生效的)
<!-- 使用Apache HttpClient替换Feign原生httpclient --> <dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-httpclient</artifactId> <version>${feign-httpclient}</version> </dependency>
按照上面3个步骤添加好依赖后,我们可以很自由的使用User对象来传递get请求的参数了,是不是很优雅有木有。
但是一波三折,我发现服务端接受到的值都是null。因此我只能这么搞了
@FeignClient("microservice-provider-user") public interface UserFeignClient { @RequestMapping(value = "/user", method = RequestMethod.GET) public PageBean<User> get(@RequestBody User user); }
竟然在get请求里加上这么一个注解。结果,好使了。哈哈,完美