前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家Spring】Feign发送Get请求时,采用POJO对象传递参数的最终解决方案 Request method 'POST' not supported (附带其余好几个坑)

【小家Spring】Feign发送Get请求时,采用POJO对象传递参数的最终解决方案 Request method 'POST' not supported (附带其余好几个坑)

作者头像
YourBatman
发布2019-09-03 16:36:44
8.1K5
发布2019-09-03 16:36:44
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦
前言

spring cloud技术栈里面,Feign可以使得我们的rest调用和调用本地方法一样方便。但是它真的有非常多的坑,苦不堪言啊。本文将描述我们最为常遇到的坑:

Feign发送Get请求时,采用POJO传递参数 Request method ‘POST’ not supported

坑 例举
Feign发送Get请求时,采用POJO传递参数的坑

在使用Feign client来调用Get请求接口时,如果方法的参数是一个对象,例如:

代码语言:javascript
复制
@FeignClient("microservice-provider-user")
public interface UserFeignClient {

  @RequestMapping(value = "/user", method = RequestMethod.GET)
  public PageBean<User> get(User user);
  
}

我们想得好好的。分页查询,查询条件用POJO的User对象进行包装进去。但奈何:在调试的时候你会一脸懵逼,因为报了如下错误:

代码语言:javascript
复制
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的源码中发现了这个:

代码语言:javascript
复制
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
代码语言:javascript
复制
<dependency>
 <groupId>org.apache.httpcomponents</groupId>
 <artifactId>httpclient</artifactId>
 <version>4.5.3</version>
</dependency>
  1. 配置上此依赖(此依赖不可少 否则不生效的)
代码语言:javascript
复制
<!-- 使用Apache HttpClient替换Feign原生httpclient --> 
<dependency>
  <groupId>com.netflix.feign</groupId>
  <artifactId>feign-httpclient</artifactId>
  <version>${feign-httpclient}</version>
</dependency>

按照上面3个步骤添加好依赖后,我们可以很自由的使用User对象来传递get请求的参数了,是不是很优雅有木有。

但是一波三折,我发现服务端接受到的值都是null。因此我只能这么搞了

代码语言:javascript
复制
@FeignClient("microservice-provider-user")
public interface UserFeignClient {

 @RequestMapping(value = "/user", method = RequestMethod.GET)
 public PageBean<User> get(@RequestBody User user);
 
}

竟然在get请求里加上这么一个注解。结果,好使了。哈哈,完美

Feign 传参问题及传输Date类型参数的时差 坑

feign的调用如下:

代码语言:javascript
复制
List<LeftSeatCountOfDaysResp> getLeftSeatCountOfDays(
            @RequestParam("configType") Integer configType,
            @RequestParam("courseId") Long courseId,
            @RequestParam("startDateFrom") Date startDateFrom,
            @RequestParam("startDateTo") Date startDateTo,
            @RequestParam("level") Integer level);

我们采用了两个date类型的参数传参,结果: 我们传入的时间为:

这里写图片描述
这里写图片描述

但服务端接受到的时间为:

这里写图片描述
这里写图片描述

天啊撸,竟然出现了我们并不熟悉的14h时差,并不是我们熟悉的8个小时。feign真是天坑啊。这是SpringCloud Feign传Date类型参数的时差导致的。

备注:使用date类型传参,如果是body里面用对象传,是不会出现时差问题的。

下面说说两种解决方案:

  1. 当发送时间类型时,直接用String发送(推荐)
  2. Feign客户端实现FeignFormatterRegistrar接口自定义DateFormatRegister
代码语言:javascript
复制
@Component
    public class DateFormatRegister implements FeignFormatterRegistrar{

        public DateFormatRegister(){
        }

        @Override
        public void registerFormatters(FormatterRegistry registry) {
        registry.addConverter(Date.class, String.class, new Date2StringConverter()); 
        }

        private class Date2StringConverter implements Converter<Date,String>{

            @Override
            public String convert(Date source) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return sdf.format(source);
            }

        }
    }

服务端实现:

代码语言:javascript
复制
@Configuration
    public class WebConfigBeans {
        @Autowired
        private RequestMappingHandlerAdapter handlerAdapter;

        /**
        * 增加字符串转日期的功能
        */
        @PostConstruct
        public void initEditableValidation() {
            ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) handlerAdapter
                        .getWebBindingInitializer();
            if (initializer.getConversionService() != null) {
                GenericConversionService genericConversionService = (GenericConversionService) initializer
                            .getConversionService();
                genericConversionService.addConverter(String.class, Date.class, new String2DateConverter());
            }
        }
    }

第二种比较麻烦,但是一劳永逸,代码的优雅性比第一种好。但个人而言,还是推荐使用第一种。

Feign 传参时候使用@DateTimeFormat注解 坑
代码语言:javascript
复制
@NotNull
    @MyFuture
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date appointDate; //预定的预成班日期

比如这个字段,服务端上面用了@DateTimeFormat注解,这样的话,springMVC手机支持直接传字符串2018-03-03自动转换的。但是,但是,如果你是用client调用,那就不报错啦,报错啦。所以使用的时候,一定要注意啊,一定要注意啊。

总结:

虽然fiegn有很多坑,但咱不能说feign不好用。毕竟他比restTemplate或者httpClient还是优雅很多的,能够简化很多东西,负载均衡也做得不错,毕竟在本地就可以做。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年09月05日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 坑 例举
    • Feign发送Get请求时,采用POJO传递参数的坑
      • Feign 传参问题及传输Date类型参数的时差 坑
        • Feign 传参时候使用@DateTimeFormat注解 坑
        • 总结:
        相关产品与服务
        负载均衡
        负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档