解决java拦截器获取POST入参导致@RequestBody参数丢失问题
作者:阿小冰
java拦截器获取POST入参导致@RequestBody参数丢失
在java开发中,拦截器的使用还是比较广泛的,例如:限制ip、接口校验、用户权限校验等等,大多首选都是使用拦截器,在正式请求接口之前先做一层校验,保证接口服务一定程度上的安全和可靠性
但是小冰在使用拦截器获取json入参的时候,遇到一个问题,那就是拦截器获取一般都是用流的方式获取,一般都是能获取到的,但是会引发另外一个问题:
拦截器相关校验走完之后,接口层的@RequestBody修饰的对象参数都会失效,并且调试之后释放会报错
org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed
因为小冰用了流,并且存在流关闭,一次请求流关闭了就只能读取一次,所以到controller接口层就会报错,流的这个报错的意思就是:大哥,我都关门了,你还来找我干嘛,滚!
解决方案
于是找度娘,搜了一波,发现大多数处理方案都是相关报错,于是总结了一个小冰解决了问题的方案,如下:
- 思路:不管流关没关闭,要把流中的参数,延伸到后面的接口去用就可以了
- 需要的组织:过滤器大哥、拦截器大哥、防流失大哥、过滤器Bean大哥还有一个自定义方法小弟
- 立马上车出发:
首先上车的是:自定义方法小弟(用来获取参数-HttpRequestHelper)
/** * @description: 获取流并将想要的结果通过拼接返回 **/ public class HttpRequestHelper { public static String getBodyString(HttpServletRequest request) throws IOException { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } }
其次是:防流失大哥(防止流读取完之后就丢失了-MyRequestWrapper)
/** * @description: 防止流丢失 **/ public class MyRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public MyRequestWrapper(HttpServletRequest request) throws IOException { super(request); //返回参数字节数组 body = HttpRequestHelper.getBodyString(request).getBytes(Charset.forName("UTF-8")); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } }
然后是:过滤器大哥(将流中数据续传-HttpServletRequestFilter)
/** * @description: 使用过滤器处理流,将当前流放到一个新的request对象中 **/ public class HttpServletRequestFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper = null; if (request instanceof HttpServletRequest) { requestWrapper = new MyRequestWrapper((HttpServletRequest) request); } //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。 // 在chain.doFiler方法中传递新的request对象 if (requestWrapper == null) { chain.doFilter(request, response); } else { chain.doFilter(requestWrapper, response); } } @Override public void init(FilterConfig arg0) throws ServletException { } }
最后是:拦截器大哥(获取流数据读取入参-只是代码片段)
//放到继承HandlerInterceptor的类下的preHandle方法中指定业务位置就行啦,这里读出来的string, //这里我处理为一个json(就是入参对象的各个字段和字段值),方便操作 //构造函数的httpServletRequest参数是preHandle方法的request入参 JSONObject.parseObject(HttpRequestHelper.getBodyString(httpServletRequest));
然后运行,由于各位大哥就先走了,发现还是不对劲,好像丢了个司机,于是一回头一看,最牛的司机大哥还没上车,这是哪位大哥?答:过滤器Bean大哥
司机大哥:过滤器Bean大哥(将上面的过滤器大哥放在副驾上(在springboot的启动类中注入过滤器))
@Bean public FilterRegistrationBean httpServletRequestReplacedRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); //把过滤器大哥安放到副驾上 registration.setFilter(new HttpServletRequestFilter()); registration.addUrlPatterns("/*"); registration.addInitParameter("paramName", "paramValue"); registration.setName("httpServletRequestFilter"); registration.setOrder(1); return registration; }
然后运行,拦截获取json参数,因为有这几个大哥这么组织和坐镇,一开车,没问题,超速上高速,shua~
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。