在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~