SpringBoot 请求同一个方法兼容form格式与requestbody json格式

探讨SpringBoot中解决同一接口兼容form数据提交与json格式请求的问题,介绍自定义请求参数解析器的方法。

前言

最近在重构一个旧服务,遇见这么一个问题,旧服务是PHP服务,PHP的controller方法可以同时兼容form的请求格式与requestbody json的请求格式,但是在SpringBoot中,是不可以的,只可以支持单一模式,使用form提交就不可以使用@RequestBody注解去接收,但是这个问题必须需要解决。

问题现象

在这里演示一下所说的问题:

首先是如果使用form格式的请求:

在这里插入图片描述

我们使用@RequestBody去接收,

@PostMapping("/test")
public String test(@RequestBody Map<String, String> params) {
    System.out.println(JSON.toJSONString(params));
    return "success";
}

就会抛出:

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
	at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:235) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:149) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:127) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:648) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) [tomcat-embed-websocket-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilterInternal(ResourceUrlEncodingFilter.java:53) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) [spring-boot-1.4.1.RELEASE.jar:1.4.1.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:105) [spring-boot-actuator-1.4.1.RELEASE.jar:1.4.1.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]

可以从异常信息中看出,原因是不支持的格式,将请求方式换成application/json,即可顺利接受,但是希望兼容form的请求格式,使用@RequestParam注解,有无法兼容application/json格式:
在这里插入图片描述

@PostMapping("/test")
public String test(@RequestParam(value = "param1", defaultValue = "", required = false) String param1) {
    System.out.println(JSON.toJSONString(param1));
    return "success";
}

param1参数无法获取到值,这个让我很头疼,SpringBoot无法兼容两种方式的请求格式。

原因

Spring 3.X系列增加了新注解 @ResponseBody,@RequestBody。
@RequestBody 将HTTP请求正文转换为适合的HttpMessageConverter对象。
Spring MVC 使用HttpMessageConverter将请求对象转化为我们希望的格式,当我们在方法中加入@RequestBody注解,Spring MVC固定使用RequestMappingHandlerAdapter来解析,其中RequestMappingHandlerAdapter支持的HttpMessageConverter有四种:

ByteArrayHttpMessageConverter:转化 byte arrays.
StringHttpMessageConverter:转化 Strings.
FormHttpMessageConverter:转化 form data to/from a MultiValueMap.
SourceHttpMessageConverter:转化 to/from a javax.xml.transform.Source.

因此,对于两种请求格式的解析,是由分别不同的两种解析器去执行的,对于上面的请求,他们是不可以在同一个方法上并行执行解析的,即既有@RequestParam,也有@RequestBody

// 这样是不支持的
@PostMapping("/test")
public String test(@RequestParam(value = "param1", defaultValue = "", required = false) String param1,
							@RequestBody Map<String, Object> params) {
    System.out.println(param1);
    System.out.println(JSON.toJSONString(params));
    return "success";
}

那么怎么办呢?

解决方案

查了很多资料,也没有发现一个Spring提供的注解可以解决这个问题,有的人提出可以自己实现一个Converter去处理这种请求,但是自己实现一个Converter未免有点复杂,因此,我想起了一个类,javax.servlet.http.HttpServletRequest,通过原生的HttpServletRequest去捕捉参数,自行进行解析,废话不多说,上代码:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.util.HashMap;
import java.util.Map;

/**
 * HttpRequest格式转换工具类
 * Created by xuanguangyao on 2018/11/6.
 */
public class HttpRequestUtil {

    /**
     * 通用请求格式转换
     * @param httpServletRequest
     * @return
     */
    public static Map<String, String> commonHttpRequestParamConvert(HttpServletRequest httpServletRequest) {
        Map<String, String> params = new HashMap<>();
        try {
            Map<String, String[]> requestParams = httpServletRequest.getParameterMap();
            if (requestParams != null && !requestParams.isEmpty()) {
                requestParams.forEach((key, value) -> params.put(key, value[0]));
            } else {
                StringBuilder paramSb = new StringBuilder();
                try {
                    String str = "";
                    BufferedReader br = httpServletRequest.getReader();
                    while((str = br.readLine()) != null){
                        paramSb.append(str);
                    }
                } catch (Exception e) {
                    System.out.println("httpServletRequest get requestbody error, cause : " + e);
                }
                if (paramSb.length() > 0) {
                    JSONObject paramJsonObject = JSON.parseObject(paramSb.toString());
                    if (paramJsonObject != null && !paramJsonObject.isEmpty()) {
                        paramJsonObject.forEach((key, value) -> params.put(key, String.valueOf(value)));
                    }
                }
            }
        } catch (Exception e) {
            System.out.println("commonHttpRequestParamConvert error, cause : " + e);
        }
        return params;
    }
}

这里我使用了FastJSON作为JSON格式的转换工具,你也可以自行使用其他的进行转换,代码很简单,分析HttpServletRequest 这个类,可以发现,如果是form格式的请求,其请求参数是可以通过getParameterMap方法进行获取,但是application/json的请求方式,可能就需要自行转换拼接一下,最终转换成一个Map形式,当然你也可以转换成JSONObject格式,对象DTO格式等等,你开心就好。

<think>好的,我现在需要帮助用户解决前端请求Spring Boot接口时出现的'Required request body is missing'错误。首先,我要回顾一下用户的问题之前的对话,确保我理解他们的需求。 用户提到他们参考了站内的几个引用,这些引用解释了错误的原因,比如客户端没有发送正确的请求体,或者服务器端配置的问题。之前的回答中,我分析了错误的原因,并给出了三种解决方案:调整前端请求方式、移除不合适的注解、验证前后端一致性。现在用户再次提问,可能希望得到更详细的解释或不同的解决方法。 接下来,我需要结合用户提供的引用内容,进一步分析错误的原因。根据引用[1],这个错误通常是因为服务器期望POST请求中有数据体,但客户端没有发送。引用[3]提到,当使用GET请求而服务器端用@RequestBody时会出现问题,因为GET通常不带请求体。引用[4]指出后端可能错误地使用了@RequestParam而不是@RequestBody,或者请求格式不对。 用户现在的疑问可能在于如何具体诊断解决这个问题,尤其是结合Spring Boot后端代码调整。我需要确保回答结构清晰,分点列出原因解决方法,并参考之前的回答,但避免重复,同时补充更多细节。 首先,错误的原因可能有以下几点: 1. 前端使用了GET方法,而后端使用@RequestBody,这会导致请求体缺失。 2. 前端发送的请求格式不正确,比如不是JSON,或者没有设置正确的Content-Type头。 3. 后端Controller的参数注解使用错误,比如用@RequestParam或@PathVariable而不是@RequestBody。 4. 请求体完全为空,即使允许为空,也需要发送{}。 解决方案需要针对这些原因逐一处理: - 检查HTTP方法是否正确,确保POST/PUT/PATCH。 - 前端确保发送JSON数据,并设置Content-Type为application/json。 - 后端使用@RequestBody注解,并正确配置参数类型,比如Map或DTO对象。 - 允许空请求体的情况下,发送空的JSON对象{}。 此外,用户可能需要具体的代码示例,比如后端Controller的写法,前端fetch或axios的请求示例。还要注意是否需要配置Spring Boot的配置项,比如spring.mvc.consumers来指定接受的媒体类型,但根据引用中的信息,这可能不是主要问题,重点还是在注解请求格式上。 用户可能还会混淆@RequestBody@RequestParam的使用场景,需要进一步解释两者的区别,以及何时使用哪个注解。比如,@RequestBody用于接收请求体中的JSON/XML数据,而@RequestParam用于接收URL参数或表单数据。 另外,引用[3]中的例子提到,即使参数为空,也需要发送空对象{},否则会报错。这可能是一个容易被忽视的地方,需要特别提醒用户。 最后,确保回答中包含相关的问题,帮助用户扩展知识,比如如何选择合适的HTTP方法,如何处理CORS问题,或者如何使用Swagger进行API测试文档生成。 总结来说,我需要组织一个结构清晰的回答,分原因解决方案,每个部分都结合用户提供的引用之前的回答,给出具体的步骤代码示例,确保用户能够一步步排查解决问题。</think>### Spring Boot接口报错 `Required request body is missing` 的详细原因解决方案 #### 一、错误原因分析 1. **HTTP 方法不匹配** - 后端接口使用 `@RequestBody` 注解接收参数时,必须通过 **POST/PUT/PATCH** 等支持请求体的方法调用。若前端误用 **GET 请求**,由于 GET 方法默认无请求体,会直接触发此错误[^1][^3]。 - 示例: ```java @GetMapping("/user") // GET 方法支持请求体 public User getUser(@RequestBody UserRequest request) { ... } ``` 2. **请求体缺失或格式错误** - 前端未发送任何请求体,或请求格式JSON(如使用 `x-www-form-urlencoded` 但未配置 `@RequestBody` 兼容的解析器)。 - 未设置请求头 `Content-Type: application/json`,导致后端无法解析请求体[^4]。 3. **后端参数注解错误** - 错误地将 `@RequestParam` 或 `@PathVariable` 用于接收请求体参数(适用于 URL 参数或路径变量),而非 `@RequestBody`[^4]。 - 示例: ```java @PostMapping("/create") public void createUser(@RequestParam("data") String data) { ... } // 错误:应使用 @RequestBody ``` 4. **空请求体未正确处理** - 即使接口允许空参数,仍需前端发送空 JSON 对象 `{}`。若完全省略请求体,Spring 会抛出异常[^3]。 --- #### 二、解决方案 ##### 1. 调整前端请求配置 - **确保使用 POST/PUT/PATCH 方法**,并明确设置请求请求体: ```javascript // Fetch API 示例 fetch('/api/user', { method: 'POST', headers: { 'Content-Type': 'application/json' // 必须声明 }, body: JSON.stringify({ name: "John", age: 25 }) // 即使为空也需传 {} }); ``` - **验证请求格式**: 使用开发者工具(如 Chrome Network 面板)检查请求体是否为有效 JSON,且无语法错误。 ##### 2. 修正后端 Controller 注解 - **使用 `@RequestBody` 接收请求体**,并匹配参数类型: ```java @PostMapping("/create") public ResponseEntity<String> createUser(@RequestBody UserDTO userDTO) { // 处理逻辑 } ``` 若允许空请求体,可结合 `required = false`(需前端至少发送 `{}`): ```java @RequestBody(required = false) Map<String, String> params ``` - **避免混淆注解**: - `@RequestParam`:接收 URL 查询参数(如 `/user?id=123`)。 - `@PathVariable`:接收路径参数(如 `/user/{id}`)。 - `@RequestBody`:仅用于请求体数据(如 JSON/XML)。 ##### 3. 配置 Spring Boot 支持请求体 - 若业务允许空请求体,可自定义参数解析器或配置全局默认值: ```java @PostMapping("/search") public ResponseEntity<?> search(@RequestBody(required = false) SearchRequest request) { if (request == null) { request = new SearchRequest(); // 提供默认值 } // 处理逻辑 } ``` ##### 4. 验证请求响应日志 - **启用 Spring Boot 请求日志**,在 `application.properties` 中添加: ```properties logging.level.org.springframework.web=DEBUG ``` 通过日志检查实际接收的请求方法、头信息体内容。 --- #### 三、常见误区验证步骤 - **误区一**:认为 GET 请求可通过 `@RequestBody` 传参。 **验证**:改用 POST 方法或通过 `@RequestParam` 接收参数[^3]。 - **误区二**:省略空请求体。 **验证**:即使无参数,前端仍需发送 `{}`。 - **误区三**:请求头 `Content-Type` 实际数据格式不匹配。 **验证**:确保 JSON 数据 `application/json` 头一一对应。 --- #### 四、扩展工具调试技巧 1. **使用 Postman 或 Swagger 测试接口** - 直接构造请求体,排除前端代码干扰。 - 示例: ![Postman 请求示例](https://example.com/postman-example.png) 2. **后端断点调试** 在 Controller 方法入口处设置断点,观察参数是否成功绑定。 --- ### 相关问题 1. **如何区分 `@RequestBody` `@RequestParam` 的使用场景?** 2. **Spring Boot 中如何全局处理请求体解析异常?** 3. **前端使用 Axios 发送 POST 请求时需要注意哪些配置?** 通过上述步骤,可系统性解决 `Required request body is missing` 错误,并避免未来类似问题。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值