Spring校验未通过时如何获取完整请求体?
解决Spring MVC校验异常时获取完整请求体的方案
默认情况下,Spring MVC的请求输入流只能读取一次,@RequestBody注解已经消费了请求体,当校验失败抛出MethodArgumentNotValidException时,异常处理器无法再次读取请求体。以下是两种可行的解决方法:
方法一:通过Filter全局缓存请求体
这种方式适合所有接口统一处理,无需修改Controller代码。
- 自定义请求体缓存Filter
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.ContentCachingRequestWrapper; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class RequestBodyCachingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 将请求包装为可缓存请求体的对象 ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); filterChain.doFilter(wrappedRequest, response); } }
- 注册Filter到Spring容器
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<RequestBodyCachingFilter> requestBodyCachingFilter() { FilterRegistrationBean<RequestBodyCachingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new RequestBodyCachingFilter()); registrationBean.addUrlPatterns("/*"); // 设置最高优先级,确保在Spring MVC的其他过滤器前执行 registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); return registrationBean; } }
- 在异常处理器中读取缓存的请求体
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.util.ContentCachingRequestWrapper; import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public Object handleValidationException(MethodArgumentNotValidException ex, HttpServletRequest request) { String requestBody = ""; if (request instanceof ContentCachingRequestWrapper) { ContentCachingRequestWrapper wrappedRequest = (ContentCachingRequestWrapper) request; byte[] bodyBytes = wrappedRequest.getContentAsByteArray(); requestBody = new String(bodyBytes, StandardCharsets.UTF_8); } // 整理校验错误信息 String errorMsg = ex.getBindingResult().getFieldErrors().stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .collect(Collectors.joining("; ")); // 返回包含请求体和错误信息的响应 return "{\"requestBody\": \"" + requestBody + "\", \"errorMsg\": \"" + errorMsg + "\"}"; } }
方法二:在Controller方法中直接获取缓存请求体
如果只需要针对特定接口处理,可以直接在Controller方法中注入ContentCachingRequestWrapper,读取缓存的请求体并暂存,异常处理器中取出使用。
- Controller方法中保存请求体
import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.ContentCachingRequestWrapper; import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; @RestController public class DemoController { @PostMapping("/demo") public String demo(@Validated @RequestBody DemoPOJO pojo, ContentCachingRequestWrapper request) { // 读取缓存的请求体并存入请求属性 String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8); request.setAttribute("REQUEST_BODY", requestBody); // 业务逻辑处理 return "success"; } }
- 异常处理器中取出请求体
@ExceptionHandler(MethodArgumentNotValidException.class) public Object handleValidationException(MethodArgumentNotValidException ex, HttpServletRequest request) { String requestBody = (String) request.getAttribute("REQUEST_BODY"); String errorMsg = ex.getBindingResult().getFieldErrors().stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .collect(Collectors.joining("; ")); return "{\"requestBody\": \"" + requestBody + "\", \"errorMsg\": \"" + errorMsg + "\"}"; }
内容的提问来源于stack exchange,提问作者ildvzg68472
相关产品推荐
相关产品推荐

