Spring REST调用中x-www-form-urlencoded数组填充不一致问题
场景复现
你在Spring Boot应用中定义了处理JSON和form-urlencoded格式的PUT端点,其中JSON格式请求能正常接收数据,但使用curl -d传递form-urlencoded数据时,PutDataRequest对象无法被正确填充;而通过URL查询参数传递时则能成功绑定。
控制器代码
@RestController @RequestMapping(value = "/rest/api", produces = MediaType.APPLICATION_JSON_VALUE) public class RestApiController { @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) private ReturnType putRestApiTypeJson( @PathVariable("id") String id, @ModelAttribute PutDataRequest request) { System.out.println(); return null; } @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) private ReturnType putRestApiTypeUrlEncoded( @PathVariable("id") String id, @ModelAttribute PutDataRequest request) { System.out.println(); return null; } }
请求对象代码
@Data @AllArgsConstructor @NoArgsConstructor public class PutDataRequest { Set<String> characters = new HashSet<>(); Set<String> movies = new HashSet<>(); }
测试curl命令
- 失败案例:
curl -X PUT 'http://localhost:some-port/rest/api' -d 'characters=Some%20Name%26movies=Some%20Title' -H 'Content-Type: application/x-www-form-urlencoded'
- 成功案例:
curl -X PUT 'http://localhost:some-port/rest/api?characters=Some%20Name%26movies=Some%20Title' -H 'Content-Type: application/x-www-form-urlencoded'
问题原因
1. 请求URL与控制器映射不匹配
控制器的PUT端点映射为/rest/api/{id},但失败案例的curl命令中缺少路径变量id,导致请求无法匹配到对应的处理方法,自然无法完成数据绑定。
2. curl参数格式错误
你在-d参数中使用%26作为键值对分隔符,但%26是&的URL编码形式。在-d参数中应直接用&分隔不同键值对,curl会自动对参数值进行URL编码。错误写法会让Spring将整个字符串解析为单个参数(键为characters,值为Some Name&movies=Some Title),无法识别movies属性。
3. Spring默认不解析PUT请求的form-urlencoded请求体
根据Servlet规范,只有POST请求的application/x-www-form-urlencoded数据会被自动解析到request.getParameter()中,PUT请求的不会。而@ModelAttribute默认从request.getParameter()中获取数据,因此即使请求体中有正确的form数据,也无法完成绑定。
解决方案
1. 修正curl命令
- 补充路径变量
id以匹配控制器映射 - 使用
&正确分隔键值对,传递集合元素时重复参数名:
# 单个元素 curl -X PUT 'http://localhost:some-port/rest/api/123' -d 'characters=Some Name&movies=Some Title' -H 'Content-Type: application/x-www-form-urlencoded' # 多个集合元素 curl -X PUT 'http://localhost:some-port/rest/api/123' -d 'characters=Name1&characters=Name2&movies=Title1' -H 'Content-Type: application/x-www-form-urlencoded'
2. 配置Spring支持PUT请求的form数据解析
在Spring Boot的application.properties中添加配置,启用PUT请求的form数据解析:
spring.mvc.formcontent.put.enabled=true
该配置会让Spring将PUT请求体中的form数据解析到request.getParameter()中,@ModelAttribute即可正常绑定。
3. 替换@ModelAttribute为@RequestBody(可选)
如果不想依赖上述配置,可使用@RequestBody直接解析请求体中的form数据,无需额外配置:
@PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) private ReturnType putRestApiTypeUrlEncoded( @PathVariable("id") String id, @RequestBody PutDataRequest request) { System.out.println(request.getCharacters()); System.out.println(request.getMovies()); return null; }
内容的提问来源于stack exchange,提问作者DivDiff

