Spring Boot项目中如何定义无需Content-Type请求头的REST服务
问题原因
你遇到的400错误是Spring MVC的默认消息转换逻辑导致的:@RequestBody注解依赖Content-Type请求头选择对应的HttpMessageConverter来解析请求体,如果客户端不传该头,Spring会默认将请求体识别为application/octet-stream(二进制流),找不到能将二进制流转为Document对象的转换器,因此请求在进入控制器方法前就被拦截,返回400错误。
解决方案
提供两种常用实现方案,可根据业务场景选择:
方案1:全局过滤器补全默认Content-Type(推荐,业务侵入小)
通过过滤器对未携带Content-Type的指定接口请求,自动填充你需要的默认类型(比如接口用JSON就填application/json,XML就填application/xml),完全不修改原有业务代码。
- 定义过滤器实现逻辑:
import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import java.io.IOException; import java.util.Collections; import java.util.Enumeration; public class DefaultContentTypeFilter implements Filter { // 按你的接口实际接收格式修改默认值 private static final String DEFAULT_CONTENT_TYPE = "application/json"; private static final String CONTENT_TYPE_HEADER = "Content-Type"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; String contentType = httpRequest.getHeader(CONTENT_TYPE_HEADER); // 仅对未传Content-Type的请求做处理 if (contentType == null || contentType.isBlank()) { HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(httpRequest) { @Override public String getContentType() { return DEFAULT_CONTENT_TYPE; } @Override public String getHeader(String name) { if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) { return DEFAULT_CONTENT_TYPE; } return super.getHeader(name); } @Override public Enumeration<String> getHeaders(String name) { if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) { return Collections.enumeration(Collections.singletonList(DEFAULT_CONTENT_TYPE)); } return super.getHeaders(name); } }; chain.doFilter(wrapper, response); return; } chain.doFilter(request, response); } }
- 注册过滤器,仅对需要的接口生效:
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<DefaultContentTypeFilter> defaultContentTypeFilter() { FilterRegistrationBean<DefaultContentTypeFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new DefaultContentTypeFilter()); // 配置需要生效的接口路径,多个接口可以追加 registrationBean.addUrlPatterns("/mock/iban/payment"); registrationBean.setOrder(1); return registrationBean; } }
方案2:手动读取请求体,跳过Spring自动转换
如果仅单个接口有该需求,可以移除@RequestBody注解,自行读取解析请求体,不需要额外加过滤器:
import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; @RestController @RequestMapping(value="/mock/iban/") public class IbanController { private final ObjectMapper objectMapper = new ObjectMapper(); @PostMapping(value="/payment") ResponseEntity<PaymentResponse> payment(HttpServletRequest request) throws IOException { // 自行读取请求体字符串 String requestBody = request.getReader().lines().reduce("", String::concat); // 手动解析为Document对象 Document doc = objectMapper.readValue(requestBody, Document.class); PaymentResponse response = new PaymentResponse(); return new ResponseEntity<>(response,HttpStatus.OK); } }
注意事项
如果你的接口需要支持多种请求格式,可根据实际请求内容调整默认Content-Type,或者在方案2的逻辑中自行判断请求体格式选择对应的解析方式。
内容的提问来源于stack exchange,提问作者Nima MashhadiZadeh
相关产品推荐
相关产品推荐

