實(shí)戰(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 對象中。然而,當(dāng) API 需要同時接收多個對象時,直接使用多個 @RequestBody 會導(dǎo)致 HttpMessageNotReadableException 異常。究其原因,這是由于 HttpServletRequest 的輸入流只能被讀取一次,第二個 @RequestBody 無法再次獲取數(shù)據(jù)。
本文將深入剖析這一問題的本質(zhì),并提供兩種不同的解決方案:
- 使用 DTO 進(jìn)行封裝(適用于前端可以調(diào)整數(shù)據(jù)格式的場景)。
- 自定義 HttpServletRequestWrapper(適用于無法修改前端請求結(jié)構(gòu)的情況)。
通過這些方法,你可以在 Spring Boot 3.4 項(xiàng)目中靈活應(yīng)對復(fù)雜的 JSON 請求體解析問題。
解決方案
方法 1:使用 DTO 進(jìn)行封裝
我們可以創(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ù)是一項(xiàng)常見但容易踩坑的挑戰(zhàn)。本文提供了兩種解決方案:
- DTO 封裝方式適用于可以修改前端請求格式的場景,簡單易用,但需要前端配合調(diào)整 JSON 結(jié)構(gòu)。
- HttpServletRequestWrapper 方案適用于無法修改前端數(shù)據(jù)格式的情況,能夠確保多個 @RequestBody 參數(shù)的正常解析。
在實(shí)際開發(fā)中,你可以根據(jù)項(xiàng)目需求選擇合適的方案,從而更高效地處理復(fù)雜的 JSON 請求體解析。希望本指南能幫助你輕松應(yīng)對 Spring Boot 3.4 的多 @RequestBody 解析問題!