知識庫 / Spring / Spring MVC RSS 訂閱

讀取 Spring 中的 HttpServletRequest 多次

Jackson,Spring MVC
HongKong
10
09:53 PM · Dec 05 ,2025

1. 引言

本教程將指導您使用 Spring 從 <em>HttpServletRequest</em> 中多次讀取請求體的方法。

<em>HttpServletRequest</em> 是一個接口,它暴露了 <em>getInputStream()</em> 方法,用於讀取請求體。 默認情況下,從該 <em>InputStream</em> 讀取的數據只能一次性讀取

2. Maven 依賴

我們需要添加的適當依賴是 `spring-webmvcjakarta.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 類的對象。

最後,我們創建了一個過濾器,將請求包裝器對象傳遞到過濾器鏈中。因此,我們能夠多次讀取請求。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.