解決Java項目中request流只能獲取一次的問題
問題描述
Java項目開發(fā)中可能存在以下幾種情況:
1、你需要在攔截器中統(tǒng)一攔截請求,拿到請求中的參數(shù),來做統(tǒng)一的判斷處理或者其他操作。
那問題就來了,由于request輸入流的數(shù)據(jù)只能讀取一次,所以你在攔截器中你讀取了輸入流的數(shù)據(jù),當(dāng)請求進(jìn)入后邊的Controller時,輸入流中已經(jīng)沒有數(shù)據(jù)了,導(dǎo)致獲取不到數(shù)據(jù)。
2、你項目里可能需要搞一個統(tǒng)一的異常處理器,然后想在異常處理器中把發(fā)生異常的接口地址,方法名,以及請求的參數(shù)記錄到日志里或者直接發(fā)送給你配置的告警系統(tǒng),比如發(fā)送給釘釘群通知。這種情況下,因為你前邊controller已經(jīng)獲取過一次request輸入流了,在后邊的異常處理器里你還想再從request輸入流中拿到請求參數(shù)等信息,所以也會出現(xiàn)request流只能讀取一次的錯誤。
以上兩種情況是開發(fā)中比較常見的,當(dāng)然除此之外,別的場景下你可能也會遇到request流只能讀取一次的錯誤,所以今天就來講一下如果遇到這種情況該怎么解決。
產(chǎn)生原因
1、一個InputStream對象在被讀取完成后,將無法被再次讀取,始終返回-1;
2、InputStream并沒有實現(xiàn)reset方法(可以重置首次讀取的位置),無法實現(xiàn)重置操作;
解決方法
一、引入依賴
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.7</version> </dependency>
二、自定義Wrapper類來包裝HttpServletRequest
我們需要寫一個自定義包裝類,并繼承HttpServletRequestWrapper
import org.apache.commons.io.IOUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @描述 包裝HttpServletRequest * MyServletRequestWrapper + RequestReplaceFilter 的作用是: * 解決異常處理器中拿post請求的json參數(shù)時,報request流只能讀一次的錯 * 原因是 request.getReader() 和 request.getInputStream() 都是只能調(diào)用一次 * 所以我這里要使用 HttpServletRequestWrapper 來實現(xiàn)自定義的 MyServletRequestWrapper包裝類 * 把request里的 body 保存在 MyServletRequestWrapper中, 并且重寫 getInputStream()方法 * 然后所有的request都在RequestReplaceFilter中被轉(zhuǎn)換成了我自定義的HttpServletRequestWrapper * 然后獲取 body時就都是調(diào)用 MyServletRequestWrapper中的 getBody()方法了 * @創(chuàng)建人 caoju */ public class MyServletRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public MyServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = IOUtils.toByteArray(super.getInputStream()); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { return new RequestBodyCachingInputStream(body); } private class RequestBodyCachingInputStream extends ServletInputStream { private byte[] body; private int lastIndexRetrieved = -1; private ReadListener listener; public RequestBodyCachingInputStream(byte[] body) { this.body = body; } @Override public int read() throws IOException { if (isFinished()) { return -1; } int i = body[lastIndexRetrieved + 1]; lastIndexRetrieved++; if (isFinished() && listener != null) { try { listener.onAllDataRead(); } catch (IOException e) { listener.onError(e); throw e; } } return i; } @Override public boolean isFinished() { return lastIndexRetrieved == body.length - 1; } @Override public boolean isReady() { return isFinished(); } @Override public void setReadListener(ReadListener listener) { if (listener == null) { throw new IllegalArgumentException("listener cann not be null"); } if (this.listener != null) { throw new IllegalArgumentException("listener has been set"); } this.listener = listener; if (!isFinished()) { try { listener.onAllDataRead(); } catch (IOException e) { listener.onError(e); } } else { try { listener.onAllDataRead(); } catch (IOException e) { listener.onError(e); } } } @Override public int available() throws IOException { return body.length - lastIndexRetrieved - 1; } @Override public void close() throws IOException { lastIndexRetrieved = body.length - 1; body = null; } } }
三、創(chuàng)建過濾器,通過過濾器包裝原有的request對象
import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @描述 * @創(chuàng)建人 caoju */ @Component public class RequestReplaceFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (!(request instanceof MyServletRequestWrapper)) { request = new MyServletRequestWrapper(request); } filterChain.doFilter(request, response); /*//如果有文件上傳的業(yè)務(wù)場景,需要用下面的代碼進(jìn)行處理,不然文件上傳的流會有問題 String contentType = request.getContentType(); //如果contentType是空 //或者contentType是多媒體的上傳類型則忽略,不進(jìn)行包裝,直接return if (contentType == null) { filterChain.doFilter(request, response); return; }else if(request.getContentType().startsWith("multipart/")){ filterChain.doFilter(request, response); return; }else if (!(request instanceof MyServletRequestWrapper)) { request = new MyServletRequestWrapper(request); } filterChain.doFilter(request, response); */ } }
通過以上幾步,我們就實現(xiàn)了把request里的 body 保存在 MyServletRequestWrapper中的效果
就可以在整個請求鏈路中任何地方去重復(fù)的獲取request流了
四、使用案例
配置好之后,就可以在整個請求鏈路中任何地方去重復(fù)的獲取request流了。
比如,你可以在請求剛進(jìn)來時,在過濾器或者攔截器里拿到request對象,再拿到request對象的流數(shù)據(jù),去做一些事情,或者你也可以在請求即將結(jié)束時,在統(tǒng)一的異常處理器中拿到request對象,拿到request對象流數(shù)據(jù)里請求的json參數(shù);等等等等,還有其他很多你想使用的場景,都可以這么做。
下面是在代碼中利用RequestContextHolder獲取request對象,拿到request對象后就可以獲取請求方式、請求url、以及請求參數(shù)這些數(shù)據(jù)了。如果你在某些地方也有需要打印記錄請求方式、請求url、請求參數(shù)的這些需求,那可以直接復(fù)制粘貼我下邊的代碼就ok了
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); String mode = ""; String methodUrl = ""; String param = ""; if (requestAttributes != null) { ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; HttpServletRequest request = attributes.getRequest(); //請求方式 mode = request.getMethod(); //方法URL methodUrl = request.getRequestURI(); if(mode.equals(HttpMethod.GET.name())){ param = request.getQueryString(); } if(mode.equals(HttpMethod.POST.name())){ param = getJsonRequest(request); } }
/** * 獲取Request中的JSON字符串 * @param request * @return * @throws IOException */ public static String getJsonRequest(HttpServletRequest request) { StringBuilder sb = new StringBuilder(); try (BufferedReader reader = request.getReader();) { char[] buff = new char[1024]; int len; while ((len = reader.read(buff)) != -1) { sb.append(buff, 0, len); } } catch (IOException e) { log.error("POST請求參數(shù)獲取異常", e); } return sb.toString(); }
ok,到這里解決request流只能獲取一次的問題就搞定了
希望對你有所幫助
以上就是解決Java項目中request流只能獲取一次的問題的詳細(xì)內(nèi)容,更多關(guān)于Java request流獲取一次的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Spring Cloud Consul 實現(xiàn)服務(wù)注冊和發(fā)現(xiàn)
這篇文章主要介紹了Spring Cloud Consul 實現(xiàn)服務(wù)注冊和發(fā)現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03東方通TongWeb結(jié)合Spring-Boot使用的實現(xiàn)
本文主要介紹了東方通TongWeb結(jié)合Spring-Boot使用的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07JAVA實戰(zhàn)項目實現(xiàn)客戶選購系統(tǒng)詳細(xì)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java實現(xiàn)一個簡單的客戶選購系統(tǒng),大家可以在過程中查缺補漏,提升水平2021-10-10Mybatis-Plus自動填充更新操作相關(guān)字段的實現(xiàn)
這篇文章主要介紹了Mybatis-Plus自動填充更新操作相關(guān)字段的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12