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)文章
Spring MVC @GetMapping和@PostMapping注解的使用方式
這篇文章主要介紹了Spring MVC @GetMapping和@PostMapping注解的使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
SpringBoot返回文件使前端下載的幾種方式小結(jié)
本文主要介紹了Spring Boot中幾種文件下載的方法,通過后端應(yīng)用下載文件并進(jìn)行業(yè)務(wù)處理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
淺談java web中常用對象對應(yīng)的實(shí)例化接口
下面小編就為大家?guī)硪黄獪\談java web中常用對象對應(yīng)的實(shí)例化接口。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
SpringBoot+Redis執(zhí)行l(wèi)ua腳本的方法步驟
這篇文章主要介紹了SpringBoot+Redis執(zhí)行l(wèi)ua腳本的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
Java8新特性之Collectors.joining()實(shí)例詳解
在項(xiàng)目中我們常常要對list集合的數(shù)據(jù)做一些字符串拼接/處理等相關(guān)操作,下面這篇文章主要給大家介紹了關(guān)于Java8新特性之Collectors.joining()的相關(guān)資料,需要的朋友可以參考下2023-01-01
SpringBoot在IDEA中實(shí)現(xiàn)熱部署(JRebel實(shí)用版)
這篇文章主要介紹了SpringBoot在IDEA中實(shí)現(xiàn)熱部署(JRebel實(shí)用版),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05

