1. 引言
本教程將指導您使用 Spring 從 <em>HttpServletRequest</em> 中多次讀取請求體的方法。
<em>HttpServletRequest</em> 是一個接口,它暴露了 <em>getInputStream()</em> 方法,用於讀取請求體。 默認情況下,從該 <em>InputStream</em> 讀取的數據只能一次性讀取。
2. Maven 依賴
我們需要添加的適當依賴是 `spring-webmvc 和 jakarta.servlet。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.13</version>
</dependency>
此外,由於我們使用了 application/json 內容類型,因此需要添加 jackson-databind 依賴項:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>Spring 使用該庫將數據轉換為 JSON 和從 JSON 轉換為數據。
3. Spring 中的 <em >ContentCachingRequestWrapper</em >
Spring 提供了一個 <em >ContentCachingRequestWrapper</em > 類。該類提供了一個 getContentAsByteArray() 方法,用於多次讀取請求體。
該類存在一個限制:我們不能使用 getInputStream() 和 getReader() 方法多次讀取請求體。
由於此限制,該類在所有情況下都不可用。
為了克服此限制,讓我們現在看看一種更通用的解決方案。
4. 擴展 HttpServletRequest
讓我們創建一個新的類——CachedBodyHttpServletRequest,它繼承自 HttpServletRequestWrapper。 這樣,我們就不需要覆蓋 HttpServletRequest 接口的所有抽象方法。
HttpServletRequestWrapper 類有兩個抽象方法:getInputStream() 和 getReader()。 我們將覆蓋這兩個方法並創建一個新的構造函數。
4.1. 構造函數
首先,我們創建一個構造函數。在其中,我們將從實際的 InputStream 讀取
內容,並將其存儲在一個 byte[] 對象中:public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}
}因此,我們可以多次閲讀正文。
4.2. getInputStream()
接下來,我們將覆蓋 getInputStream() 方法。 我們將使用此方法讀取原始內容並將其轉換為對象。
在該方法中,我們將 創建並返回一個新的 CachedBodyServletInputStream 類對象(ServletInputStream 的一個實現)。
@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream(this.cachedBody);
}4.3. getReader()
然後,我們將覆蓋 getReader() 方法。此方法返回一個 BufferedReader 對象:
@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}5. 實現 ServletInputStream
我們將創建一個 類 – CachedBodyServletInputStream – 來實現 ServletInputStream。 在這個類中,我們將創建一個新的構造函數,並覆蓋 isFinished()、isReady() 和 read() 方法。
5.1. 構造函數
首先,我們創建一個新的構造函數,該構造函數接受一個字節數組。
內部,我們將使用該字節數組創建一個 新的 ByteArrayInputStream 實例 。之後,我們將將其分配給全局變量 cachedBodyInputStream:
public class CachedBodyServletInputStream extends ServletInputStream {
private InputStream cachedBodyInputStream;
public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
}5.2. read()
然後,我們將覆蓋 read() 方法。在該方法中,我們將調用 ByteArrayInputStream#read:。
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}5.3. isFinished()
然後,我們將覆蓋 isFinished() 方法。此方法指示 InputStream 是否有更多數據可讀。當沒有可讀字節時,它返回 true。
@Override
public boolean isFinished() {
return cachedBody.available() == 0;
}5.4. isReady()</h3
類似於地,我們將覆蓋 isReady() 方法。此方法指示 InputStream 是否已準備好進行讀取。
由於我們已經將 InputStream 複製到字節數組中,我們將返回 true 以指示它始終可用:
@Override
public boolean isReady() {
return true;
}6. 過濾器
最後,讓我們創建一個新的過濾器,以利用 CachedBodyHttpServletRequest 類。在這裏,我們將擴展 Spring 的 OncePerRequestFilter 類。該類有一個抽象方法 doFilterInternal()。
在該方法中,我們將 從實際請求對象中創建一個 CachedBodyHttpServletRequest 類的對象:
CachedBodyHttpServletRequest cachedBodyHttpServletRequest =
new CachedBodyHttpServletRequest(request);然後,我們將這個新的請求包裝器對象傳遞給過濾器鏈。因此,對 getInputStream() 方法的所有後續調用都將觸發重寫的邏輯:
filterChain.doFilter(cachedContentHttpServletRequest, response);7. 結論
在本教程中,我們快速地介紹了 ContentCachingRequestWrapper 類。我們還看到了它的侷限性。
然後,我們創建了 HttpServletRequestWrapper 類的新的實現。我們重寫了 getInputStream() 方法以返回一個 ServletInputStream 類的對象。
最後,我們創建了一個過濾器,將請求包裝器對象傳遞到過濾器鏈中。因此,我們能夠多次讀取請求。