Java如何解決發(fā)送Post請求報(bào)Stream?closed問題
springboot項(xiàng)目還是ssm等java常用框架都會(huì)有這樣的問題,解決辦法通用
問題場景
前端發(fā)送Post請求,前端返回400 Bad Request,后端Controller層接口也沒進(jìn)去,然后我就開始分析,是啥問題,我通過后端控制臺(tái)發(fā)現(xiàn)HttpMessageNotReadableException 提示信息,這個(gè)不是讀取請求的消息錯(cuò)誤發(fā)生的異常嗎?
然后我通過IDEA 的DEBUG攔截這個(gè)異常發(fā)生的位置,然后將相關(guān)的代碼從新走了一遍發(fā)現(xiàn)在
AbstractMessageConverterMethodArgumentResolver->readWithMessageConverters
EmptyBodyCheckingHttpInputMessage 是內(nèi)部類
控制臺(tái)打印warn 信息如下:
org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed
問題分析
這是因?yàn)橛腥嗽谶^濾器或者攔截器中對Request的請求體中的數(shù)據(jù)流讀取了一遍導(dǎo)致的,Springboot準(zhǔn)備讀取Body數(shù)據(jù)映射到接口的實(shí)體類參數(shù)時(shí)候失敗,發(fā)現(xiàn)流已經(jīng)沒有內(nèi)容了,因?yàn)樵诮涌跀?shù)據(jù)流傳輸使用的都是InputStream 這個(gè)流只能被讀取一次
解決辦法
將InputStream 傳輸數(shù)據(jù),緩存起來,保存到字符串中,之后用的時(shí)候?qū)⒆址D(zhuǎn)在轉(zhuǎn)換為流,那么這個(gè)字符串就能持續(xù)的被復(fù)用了
緩存數(shù)據(jù)
package com.schemautils; /** * 解決獲取post請求的請求體body只能讀取一次問題 */ import com.alibaba.fastjson.JSONObject; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; public class RequestWrapper extends HttpServletRequestWrapper { private final String body; public RequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { //防止未初始化body,我們手動(dòng)初始化body ,內(nèi)部會(huì)將body內(nèi)容初始化到InputStream里 request.getParameterMap(); //然后在讀取InputStream inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); ServletInputStream servletInputStream = new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; return servletInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public JSONObject getBody() { return JSONObject.parseObject(this.body); } }
添加過濾器并且配置緩存類
package com.schemautils; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; //獲取請求中的流,將取出來的,再次轉(zhuǎn)換成流,然后把它放入到新request對象中 //必須保證在所有過濾器之前執(zhí)行,否則就會(huì)出現(xiàn)問題(按照首字母進(jìn)行過濾器優(yōu)先級(jí)A>B>C) @WebFilter(filterName = "ACacheHttpServletRequestFilter", urlPatterns = "/") public class CacheHttpServletRequestFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(servletRequest instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); } //獲取請求中的流如何,將取出來的字符串,再次轉(zhuǎn)換成流,然后把它放入到新request對象中 // 在chain.doFiler方法中傳遞新的request對象 if(null == requestWrapper) { filterChain.doFilter(servletRequest, servletResponse); } else { filterChain.doFilter(requestWrapper, servletResponse); } } @Override public void destroy() { } }
在啟動(dòng)類上開啟掃描Filter注解
@SpringBootApplication(scanBasePackages = "com") @ServletComponentScan //開啟掃描Filter public class ApplicatioBoot { public static void main(String[] args) { SpringApplication.run(ApplicatioBoot.class,args); } }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java數(shù)據(jù)隨機(jī)分頁實(shí)現(xiàn)方案
本文主要介紹了java數(shù)據(jù)隨機(jī)分頁實(shí)現(xiàn)方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06SpringBoot超詳細(xì)講解Thymeleaf模板引擎
這篇文章主要分享了Spring Boot整合使用Thymeleaf,Thymeleaf是新一代的Java模板引擎,類似于Velocity、FreeMarker等傳統(tǒng)引擎,關(guān)于其更多相關(guān)內(nèi)容,需要的小伙伴可以參考一下2022-07-07詳解SpringBoot程序啟動(dòng)時(shí)執(zhí)行初始化代碼
這篇文章主要介紹了詳解SpringBoot程序啟動(dòng)時(shí)執(zhí)行初始化代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09java中List去除重復(fù)數(shù)據(jù)的5種方式總結(jié)
這篇文章主要給大家總結(jié)介紹了關(guān)于java中List去除重復(fù)數(shù)據(jù)的5種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01APT?注解處理器實(shí)現(xiàn)?Lombok?常用注解功能詳解
這篇文章主要為大家介紹了使用APT?注解處理器實(shí)現(xiàn)?Lombok?常用注解功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09