自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從零搭建開發(fā)腳手架之 HttpServletRequest多次讀取異常問題的因和果

開發(fā) 前端
在過濾器或者Controller中多次調(diào)用HttpServletRequest.getReader()或getInputStream()方法,會(huì)導(dǎo)致異常。

[[382286]]

本文轉(zhuǎn)載自微信公眾號(hào)「Java大廠面試官」,作者laker。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java大廠面試官公眾號(hào)。

背景

在過濾器或者Controller中多次調(diào)用HttpServletRequest.getReader()或getInputStream()方法,會(huì)導(dǎo)致異常。

給出示例代碼如下:

  1. @RequestMapping(value = "/param"
  2. private ResponseEntity<String> param(HttpServletRequest request, @RequestBody Map body){ 
  3.       // ... 
  4.       String string = IOUtils.toString(request.getInputStream()); 
  5.       // ... 

Postman請(qǐng)求如下:

 

錯(cuò)誤如下:

  1. java.lang.IllegalStateException: getInputStream() has already been called for this request 
  2.  at org.apache.catalina.connector.Request.getReader(Request.java:1222) ~[tomcat-embed-core-9.0.41.jar:9.0.41] 
  3.  at org.apache.catalina.connector.RequestFacade.getReader(RequestFacade.java:504) ~[tomcat-embed-core-9.0.41.jar:9.0.41] 
  4.  at com.laker.notes.easy.http.HttpController.param(HttpController.java:64) ~[classes/:na] 
  5.     ... 

原因

Json數(shù)據(jù)是放在Http協(xié)議的Body中的,我們需要通過request.getInputStream()或者@RequestBody(本質(zhì)也是調(diào)用request.getInputStream())獲取請(qǐng)求體內(nèi)容。

當(dāng)我們調(diào)用request.getInputStream()時(shí),可以查看其Api,其返回的是ServletInputStream繼承于InputStream。

  1. public ServletInputStream getInputStream() throws IOException; 
  2.  
  3. public abstract class ServletInputStream extends InputStream { 
  4.     // ... 

下面我們來復(fù)習(xí)下流的知識(shí):

InputStream的read方法內(nèi)部有一個(gè)position,標(biāo)志當(dāng)前讀取到的位置,讀取到最后會(huì)返回-1,表示讀取完畢。如果想要重新讀取則需要使用mark和reset方法配合使用,把position移動(dòng)到起始位置,就能從頭讀取實(shí)現(xiàn)多次讀取,但是InputStream和ServletInputStream都未重寫mark和reset方法。

所以就導(dǎo)致HttpServletRequest.getReader()或getInputStream()方法不能多次讀取。

解決辦法

使用HttpServletRequestWrapper,此類是HttpServletRequest的包裝類,基于裝飾器模式實(shí)現(xiàn)HttpServletRequest功能擴(kuò)展。我們可以通過繼承包裝類HttpServletRequestWrapper來實(shí)現(xiàn)自定義擴(kuò)展功能。

  • 我們重新定義一個(gè)容器(字節(jié)數(shù)組),把讀取到的流數(shù)據(jù)存儲(chǔ)其中供以后多次使用。
  • 重寫getReader()和getInputStream()方法,改為每次從自定義容器中獲取內(nèi)容。
  • 再配合Filter把原始的HttpServletRequest替換為我們自定義的包裝類xxxHttpServletRequestWrapper。

代碼如下:

  • CachedBodyHttpServletRequestWrapper.java
  1. public class CachedBodyHttpServletRequestWrapper extends HttpServletRequestWrapper { 
  2.     private byte[] cachedBody; 
  3.     public CachedBodyHttpServletRequestWrapper(HttpServletRequest request) throws IOException { 
  4.         super(request); 
  5.         InputStream requestInputStream = request.getInputStream(); 
  6.         this.cachedBody = StreamUtils.copyToByteArray(requestInputStream); 
  7.     } 
  8.     @Override 
  9.     public ServletInputStream getInputStream() throws IOException { 
  10.         return new CachedBodyServletInputStream(this.cachedBody); 
  11.     } 
  12.     @Override 
  13.     public BufferedReader getReader() throws IOException { 
  14.         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); 
  15.         return new BufferedReader(new InputStreamReader(byteArrayInputStream)); 
  16.     } 
  17.     public class CachedBodyServletInputStream extends ServletInputStream { 
  18.         private InputStream cachedBodyInputStream; 
  19.         public CachedBodyServletInputStream(byte[] cachedBody) { 
  20.             this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody); 
  21.         } 
  22.         @Override 
  23.         public int read() throws IOException { 
  24.             return cachedBodyInputStream.read(); 
  25.         } 
  26.         // ... 
  27.     } 
  • ContentCachingFilter.java
  1. @Order(value = Ordered.HIGHEST_PRECEDENCE) 
  2. @Component 
  3. @WebFilter(filterName = "ContentCachingFilter", urlPatterns = "/*"
  4. public class ContentCachingFilter extends OncePerRequestFilter { 
  5.  
  6.     @Override 
  7.     protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { 
  8.         System.out.println("IN  ContentCachingFilter "); 
  9.         CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(httpServletRequest); 
  10.         filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse); 
  11.     } 

擴(kuò)展思考

1.是否存在線程安全問題?

實(shí)測(cè)結(jié)果如下圖,非單例,不存在線程安全問題。

 

2.加載順序問題?

ContentCachingFilter必須在Filter鏈中的第一個(gè),否則后面使用的是非自定義包裝類而是默認(rèn)的HttpServletRequest,將無法起作用。

3.OncePerRequestFilter和Filter的區(qū)別

OncePerRequestFilter 實(shí)現(xiàn)了 Filter 接口。

  1. OncePerRequestFilter extends GenericFilterBean implements Filter{ 

在Spring中,F(xiàn)ilter默認(rèn)繼承OncePerRequestFilter。

 

OncePerRequestFilter:顧名思義,它能夠確保在一次請(qǐng)求中只通過一次filter,而需要重復(fù)的執(zhí)行。大家常識(shí)上都認(rèn)為,一次請(qǐng)求本來就只filter一次,為什么還要由此特別限定呢。

往往我們的常識(shí)和實(shí)際的實(shí)現(xiàn)并不真的一樣,經(jīng)過一番資料的查閱,此方法是為了兼容不同的web container,也就是說并不是所有的container都入我們期望的只過濾一次,servlet版本不同,執(zhí)行過程也不同,我們可以看看Spring的javadoc怎么說:

  1. * <p>As of Servlet 3.0, a filter may be invoked as part of a 
  2. * {@link javax.servlet.DispatcherType#REQUEST REQUEST} or 
  3. * {@link javax.servlet.DispatcherType#ASYNC ASYNC} dispatches that occur in 
  4. * separate threads. A filter can be configured in {@code web.xml} whether it 
  5. * should be involved in async dispatches. However, in some cases servlet 
  6. * containers assume different default configuration.  

簡單的說就是去適配了不同的web容器,以及對(duì)異步請(qǐng)求,也只過濾一次的需求。另外打個(gè)比方:如:servlet2.3與servlet2.4也有一定差異:

在servlet2.3中,F(xiàn)ilter會(huì)經(jīng)過一切請(qǐng)求,包括服務(wù)器內(nèi)部使用的forward轉(zhuǎn)發(fā)請(qǐng)求和<%@ include file=”/login.jsp”%>的情況 servlet2.4中的Filter默認(rèn)情況下只過濾外部提交的請(qǐng)求,forward和include這些內(nèi)部轉(zhuǎn)發(fā)都不會(huì)被過濾,因此此處我有個(gè)建議:我們?nèi)羰窃赟pring環(huán)境下使用Filter的話,個(gè)人建議繼承OncePerRequestFilter吧,而不是直接實(shí)現(xiàn)Filter接口。這是一個(gè)比較穩(wěn)妥的選擇

參考:

https://cloud.tencent.com/developer/article/1497822

 

責(zé)任編輯:武曉燕 來源: Java大廠面試官
相關(guān)推薦

2021-04-28 16:10:48

開發(fā)腳手架 Spring

2021-04-13 14:47:53

認(rèn)證授權(quán)Java

2021-05-13 17:02:38

MDC腳手架日志

2021-07-13 18:42:38

Spring Boot腳手架開發(fā)

2021-06-02 17:58:49

腳手架 冪等性前端

2021-07-29 18:49:49

Spring開發(fā)腳手架

2020-08-19 08:55:47

Redis緩存數(shù)據(jù)庫

2021-03-11 14:16:47

Spring Boo開發(fā)腳手架

2021-04-20 19:24:16

腳手架 Java微信

2021-09-01 10:07:43

開發(fā)零搭建Groovy

2021-03-09 17:11:09

數(shù)據(jù)庫腳手架開發(fā)

2016-08-10 14:59:41

前端Javascript工具

2023-11-21 17:36:04

OpenFeignSentinel

2021-01-07 05:34:07

腳手架JDK緩存

2018-08-30 16:08:37

Node.js腳手架工具

2018-06-11 14:39:57

前端腳手架工具node.js

2014-08-15 09:36:06

2022-07-18 07:58:46

Spring工具工具類

2016-09-07 15:35:06

VueReact腳手架

2021-12-23 10:35:32

SpringCloud腳手架架構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)