實戰(zhàn)揭秘!Spring Boot 3.4 多 @RequestBody 處理技巧,輕松應(yīng)對復(fù)雜入?yún)?/h1>
在 Spring Boot 3.4 開發(fā)過程中,@RequestBody 注解是解析 HTTP 請求體 JSON 數(shù)據(jù)的常見方式,能夠自動將數(shù)據(jù)綁定到 Java 對象中。然而,當 API 需要同時接收多個對象時,直接使用多個 @RequestBody 會導(dǎo)致 HttpMessageNotReadableException 異常。究其原因,這是由于 HttpServletRequest 的輸入流只能被讀取一次,第二個 @RequestBody 無法再次獲取數(shù)據(jù)。
本文將深入剖析這一問題的本質(zhì),并提供兩種不同的解決方案:
- 使用 DTO 進行封裝(適用于前端可以調(diào)整數(shù)據(jù)格式的場景)。
 - 自定義 HttpServletRequestWrapper(適用于無法修改前端請求結(jié)構(gòu)的情況)。
 
通過這些方法,你可以在 Spring Boot 3.4 項目中靈活應(yīng)對復(fù)雜的 JSON 請求體解析問題。
解決方案
方法 1:使用 DTO 進行封裝
我們可以創(chuàng)建一個 RequestDTO 類,將 User 和 Person 統(tǒng)一封裝。
package com.icoderoad.dto;
public class RequestDTO {
    private User user;
    private Person person;
    // getter 和 setter
}然后修改 Controller 方法:
package com.icoderoad.controller;
import com.icoderoad.dto.RequestDTO;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class MultiRequestBodyController {
    @PostMapping("/multi")
    public String handleMultiple(@RequestBody RequestDTO dto) {
        return "Received: " + dto.getUser().getName() + " and " + dto.getPerson().getName();
    }
}這種方式雖然簡單,但要求前端調(diào)整 JSON 數(shù)據(jù)格式。
方法 2:自定義 HttpServletRequestWrapper 允許多次讀取請求體
如果前端無法調(diào)整請求格式,我們可以使用 HttpServletRequestWrapper 解決 InputStream只能讀取一次的問題。
自定義 CachedBodyHttpServletRequest
package com.icoderoad.wrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.util.StreamUtils;
import java.io.*;
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
    private final byte[] cachedBody;
    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
    }
    @Override
    public ServletInputStream getInputStream() {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener listener) {}
            @Override
            public int read() {
                return byteArrayInputStream.read();
            }
        };
    }
}創(chuàng)建過濾器攔截請求
package com.icoderoad.filter;
import com.icoderoad.wrapper.CachedBodyHttpServletRequest;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class CachedBodyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            CachedBodyHttpServletRequest wrappedRequest = new CachedBodyHttpServletRequest((HttpServletRequest) request);
            chain.doFilter(wrappedRequest, response);
        } else {
            chain.doFilter(request, response);
        }
    }
}配置過濾器
package com.icoderoad.config;
import com.icoderoad.filter.CachedBodyFilter;
import jakarta.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<Filter> cachedBodyFilter() {
        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CachedBodyFilter());
        registrationBean.setUrlPatterns(List.of("/*"));
        return registrationBean;
    }
}結(jié)論
在 Spring Boot 3.4 版本中,同時解析多個 @RequestBody 參數(shù)是一項常見但容易踩坑的挑戰(zhàn)。本文提供了兩種解決方案:
- DTO 封裝方式適用于可以修改前端請求格式的場景,簡單易用,但需要前端配合調(diào)整 JSON 結(jié)構(gòu)。
 - HttpServletRequestWrapper 方案適用于無法修改前端數(shù)據(jù)格式的情況,能夠確保多個 @RequestBody 參數(shù)的正常解析。
 
在實際開發(fā)中,你可以根據(jù)項目需求選擇合適的方案,從而更高效地處理復(fù)雜的 JSON 請求體解析。希望本指南能幫助你輕松應(yīng)對 Spring Boot 3.4 的多 @RequestBody 解析問題!















 
 
 



















 
 
 
 