SpringBoot攔截器讀取流後不能再讀取(詳解)
阿新 • • 發佈:2019-12-31
在SpringBoot的攔截器中通過流 ( request.getInputStream() ) 的方式讀取body中傳來的資料會導致controller接收不到值。
這個問題其實就是一個流讀取的問題,眾所周知在Java中input流只能讀取一次,主要原因是通標記的方法來判斷流是否讀取完畢(讀取位 -1就是流讀取完畢)解決這個問題我能想到兩種方式
- 通過修改標記的方式 ( inputstream.markSupported() 方法可以判斷這個流是否支援 mark 和 reset 方法。他們分別是標記 和 重新定位流。)
- 將流賦值給一個 byte[] 陣列,或者其他變數儲存起來。下載讀取流時就呼叫這個陣列就行。
第一種方法
再回到問題上來我們可以先使用第一種方法判斷 requet 中的inputStream 是否支援標記和重新定位。因為這種方式實現起來比較簡單。無需考慮太多。
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws IOException {
boolean b = request.getInputStream().markSupported();
System.out.println(b);
}
// 輸出結果為 false
複製程式碼
上述程式碼會返回一個 false 那麼很明顯,request 中的 input 流是不支援標記和重新定位的。
第二種方法
我們再考慮第二種方法,我們需要一個變數儲存這個流。並且還要保證再過濾器中和controller中都要拿到這個變數。直接定義一個全域性變數獲取修改傳值方式,都是可以的。全域性變數這種方式我就不演示了。下面是改變傳值方式的 demo
public boolean preHandle(HttpServletRequest request,Object handler) throws IOException {
ServletInputStream inputStream1 = request.getInputStream();
// 各種對 inputStream1 處理的操作...
Object obejct = inputStream1;
request.setAttribute("Params",obejct);
}
複製程式碼
這樣就可以再controller那邊就可以直接獲取 Attribute 中的值。
但是!這樣有很大的侷限性,例如: 我已經寫好了大多數的controller方法體。這時再改用這種方式傳值。對於開發人員是一種莫大的痛苦 -_- 於是通過不屑的百度查詢到另一種方法 一一一 改寫HttpServletRequestWrapper方法。為什麼是這個方法?因為他實現了 HttpServletRequest 這個介面。也是我們攔截器接收的 request 要求的型別。首先我們先看一張圖。** 注意 filter 和 inteceptor 中間。 **
通過上方這個圖我們可以知道,在 Filter 和 Inteceptor 中間有一層Servlet。而Servlet就是提交request的地方。所以我們要重寫HttpServletRequest方法只能在Servlet之前。也就是filter 中。下面就是直接上程式碼了- 重寫 HttpServletRequest
package com.xqw.kyg.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.util.StreamUtils;
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper{
private byte[] requestBody = null;//用於將流儲存下來
public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read(){
return bais.read(); // 讀取 requestBody 中的資料
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) { }
};
}
@Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
複製程式碼
- 編寫Filter
package com.xqw.kyg.filter;
import com.xqw.kyg.util.MyHttpServletRequestWrapper;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class HttpServletRequestReplacedFilter 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 MyHttpServletRequestWrapper((HttpServletRequest) request);
}
if(requestWrapper == null) {
chain.doFilter(request,response);
} else {
chain.doFilter(requestWrapper,response);
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {}
}
複製程式碼