今天遇到一个需求,需要异步导出一些数据到文件中,因为导出的数据量比较大,所以接收到导出请求之后,就将需要导出的数据的ID写入了MQ中,消费端接收到MQ的消息之后,然后通过Feign调用其他的服务进行数据导出。

通过Feign调用的代码如下:

1
2
3
4
5
6
7
8
@FeignClient(name = ConstantsUtil.ORDER_SERVICE)
public interface OrderService {

@Headers({"feign: true"})
@PostMapping(value = "/orders/save")
void save(@RequestParam("json") String json);

}

开发的过程中,数据量比较少,通过此方法可以正常的完成调用,但是在测试环境,当数据量(json)稍微大一点之后,服务就无法调用了,直接报错了。

通过查询日志发现,Feign调用时,请求的地址居然是http://order.service?json=xxxxxxx,由于json的数据量比较大,所以请求的时候直接就报错了。

看到这里之后,基本可以确定问题了:传输的数据量太大了导致URL太长了,解决方案就是把数据放在RequestBody中传输,修改代码如下:

1
2
3
4
5
6
7
8
@FeignClient(name = ConstantsUtil.ORDER_SERVICE)
public interface OrderService {

@Headers({"feign: true"})
@PostMapping(value = "/orders/save")
void save(@RequestBody String json);

}

Feign调用的时候,把数据放在RequestBody中,在被调用端接收数据的时候,也用@RequestBody绑定要接收的参数即可。

经过上面的调整,再在测试环境保存数据,调用的时候就没有报错了。

在使用Feign进行参数绑定时,我们通过会用到三个注解:@RequestParam、@RequestBody、@PathVariable,这三者之间有何区别呢?

  • @RequestParam
    一般用来处理Content-Typeapplication/x-www-form-urlencoded编码的内容。
    即可以传递单个参数,也可以传递对象。如果传递对象,其实就是利用key-value组成map,然后反射成我们需要的对象。
    注意:在Http请求中,如果不指定Content-Type则默认为application/x-www-form-urlencoded

  • @RequestBody
    一般用来处理Content-Type不为application/x-www-form-urlencoded编码的内容,比如application/json
    一般用来传递对象,也可以传递单个参数。但是GET请求不能使用@RequestBody获取数据,因为没有HttpEntity

  • @PathVariable
    一般用来映射URL中的占位符到目标方法中。

    1
    2
    @GetMapping("/order/{id}")
    JsonResult findById(@PathVariable("id") Integer id);

HTTP的GETPOST请求和Content-Type之间有什么关系呢?

GETPOST都定义了数据提交的方式:GET请求是将form数据拼装在URL后面提交;POST请求是将数据放在RequestBody中提交。

Content-Type定义了数据编码的方式,application/x-www-form-urlencoded方式是将form的数据转成K-V的形式然后形成一个字符串,拼接在URL后面,form中的多个参数以?分割;application/json方式是将数据转成json格式。

搞清楚了GETPOST请求和Content-Type之间关系之后,回到最初的那段代码,有没有觉得有点奇怪呢?

1
2
@PostMapping(value = "/orders/save")
void save(@RequestParam("json") String json);

发出一个POST(@PostMapping(value = "/orders/save") )请求,但是请求参数却被绑定在URL(@RequestParam("json") )后面。。。

其实也好理解,POST请求体里是空的,请求的参数就是在URL后面,后端取参数的时候,也是用@RequestParam取的。