项目场景:
我们在项目中经常会使用到@RequestBody这个注解来获取前端给的参数,方便又快捷,也不需要做类型转换,前端按照我们入参bean类的格式取传,我们直接就可以获取到并在代码中使用了,但是“老马”也有失蹄的时候,在使用拦截器对接口入参进行预先处理时,就发生了一个问题。
问题描述:
项目中有个拦截器,需要新拦截一个post请求的接口,并获取参数,对参数进行处理。因为是post请求,所以 request.getParamter(“data”)是获取不到的,只能通过 request.getInputStream来获取。获取源码如下:
public String getParam(HttpServletRequest request) throws IOException {
BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
sb.append(inputStr);
}
System.out.println(sb.toString());
return sb.toString();
}
入参也获取到了,拦截代码也写好了,一运行,却发现接口调不通了,,提示如下
{
“timestamp”: “2021-07-21T06:31:44.888+0000”,
“status”: 400,
“error”: “Bad Request”,
“message”: “Required request body is missing:xxxxxxx”,
“path”: “/xxxxx”
}
发现问题后很奇怪,为什么原先好好的代码不同了(遇到问题来网上找答案的小伙伴可能跟我是一模一样的心情),冲完浪才发现,我在接收参数的时候使用了 @RequestBody 这个注解,而这个注解其实也是使用 getInputStream 这个方法来获取参数的,重点是!! getInputStream 不像 getParamter 能取多次,getInputStream 在取完值后,流就用掉了,第二次再取得时候, 这个流就是空的了。(想想也对,流这个东西本来就是瞬时的,怎么可能来回的去用呢)。
解决方案:
既然想在拦截器和controller代码中都要获取参数,那么就得改造一下原来的使用方式。拦截器获取参数方法中,只加了一行代码
request.setAttribute(“body”,sb.toString());
把获取到的参数再放入request中缓存一下,这个方法就算是改造完毕
public String getParam(HttpServletRequest request) throws IOException {
BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
sb.append(inputStr);
}
System.out.println(sb.toString());
// post请求的接口在从 inputStream中读取参数后,控制层使用@RequestBody会报请求体为空的异常,
// 在这里用attribute缓存一下,同样接口也要使用attribute来接收
request.setAttribute("body",sb.toString());
return sb.toString();
}
接口代码也需要改造一下。
@RequestMapping(value = "/xxxxx",method = RequestMethod.POST)
public BaseResp doSomething(HttpServletRequest request){
BaseResp resp= new BaseResp();
Gson gson = new Gson();
try {
String body = String.valueOf(request.getAttribute("body"));
RequestBean requestBean = gson.fromJson(body,RequestBean.class);
resp= service.doSomething(requestBean);
} catch (Exception e) {
log.error("【doSomething 异常】","",e);
}
return response;
}
接口入参就不能再使用@RequestBody了,而是要使用HttpServletRequest,然后再通过 request.getAttribute 方法来获取我们在拦截器方法中存放的参数,再通过Gson(用的是谷歌的Gson)转换一下,转成我们的入参对象,就可以正常使用了。
总结:
其实还是不了解 @RequestBody 的源码,和没有发现流使用一次会没得特点,才导致出这么一个问题,不过后续此类问题也不会再犯了,也要认真接着学习鸭