You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何将POST请求体同时解析为实体和原始字符串

根因说明

Spring 不支持同时声明两个@RequestBody的本质原因是 HTTP 请求体的输入流默认只能读取一次,读完后无法再次消费,所以无法同时支持多次反序列化。针对你的需求,以下是两种成熟的实现方案:


方案1:使用Spring自带请求缓存包装类(改动最小)

用ContentCachingRequestWrapper缓存请求体字节数组,实现重复读取,不需要修改现有反序列化逻辑:

  1. 先注册请求缓存Filter,仅针对JSON接口生效
import org.springframework.web.util.ContentCachingRequestWrapper;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter(urlPatterns = "/example/*")
public class CachingRequestBodyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        if (httpServletRequest.getContentType() != null && httpServletRequest.getContentType().contains("application/json")) {
            chain.doFilter(new ContentCachingRequestWrapper(httpServletRequest), response);
            return;
        }
        chain.doFilter(request, response);
    }
}
  1. 启动类添加@ServletComponentScan注解使Filter生效,大容量请求可在配置文件调整缓存阈值:
# application.properties,根据实际请求大小调整
spring.web.content-caching.max-size=20MB
  1. 接口写法如下:
@RestController
@RequestMapping("/example")
public class ExampleController {

    @PostMapping("")
    public void example(
        @RequestBody ExampleRequest exampleRequest,
        HttpServletRequest request
    ) {
        ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
        String stringBody = new String(wrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
        
        // 用stringBody做请求校验
        // 用exampleRequest处理业务逻辑
    }
}

方案2:自定义泛型包装类(复用性更强)

如果多个接口都有同类需求,可以自定义参数解析器,一次注入同时拿到原始请求体和解析后的实体:

  1. 定义泛型包装类
public class RequestWrapper<T> {
    private String rawBody;
    private T parsedBody;

    public RequestWrapper(String rawBody, T parsedBody) {
        this.rawBody = rawBody;
        this.parsedBody = parsedBody;
    }
    // getter方法省略
}
  1. 自定义参数解析器
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import org.springframework.util.StreamUtils;

public class RequestWrapperArgumentResolver implements HandlerMethodArgumentResolver {

    private final ObjectMapper objectMapper;

    public RequestWrapperArgumentResolver(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(RequestWrapper.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        String rawBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
        Class<?> parsedType = (Class<?>) ((ParameterizedType) parameter.getGenericParameterType()).getActualTypeArguments()[0];
        Object parsedBody = objectMapper.readValue(rawBody, parsedType);
        return new RequestWrapper<>(rawBody, parsedBody);
    }
}
  1. 注册解析器到Spring MVC配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final ObjectMapper objectMapper;

    public WebConfig(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new RequestWrapperArgumentResolver(objectMapper));
    }
}
  1. 接口写法简化为:
@RestController
@RequestMapping("/example")
public class ExampleController {

    @PostMapping("")
    public void example(RequestWrapper<ExampleRequest> requestWrapper) {
        String stringBody = requestWrapper.getRawBody();
        ExampleRequest exampleRequest = requestWrapper.getParsedBody();
        // 业务逻辑
    }
}

内容的提问来源于stack exchange,提问作者lilBunnyRabbit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 03:48:03