Spring为何在application/json GET请求中解码加号?该如何处理?
一、为什么Spring会把查询参数中的+解码为空格?
这其实是HTTP标准的规定,和请求体的Content-Type无关!
URL的查询字符串(即?后面的部分)遵循application/x-www-form-urlencoded的编码规则——这个规则里明确规定,+用来表示空格(这是早期HTML表单提交的约定,后来被纳入HTTP规范)。不管你的请求体是JSON、XML还是其他格式,只要参数在URL的查询串里,Spring就会按照这个标准规则解码,把+转成空格。
你提到的SPR-6291 Bug修复,其实针对的是POST请求体中的表单数据错误解码+的问题,和URL查询参数的解码逻辑完全无关。所以这个修复不影响你遇到的场景,哪怕你的Spring版本高于5.0.0,查询参数的解码规则也不会改变。
二、如何禁用这个行为?
如果确实需要在服务器端保留原始的+号,有两种方式,但都不推荐(原因后面说明):
直接获取原始查询字符串自行解析
绕过Spring的自动解码,从HttpServletRequest中拿到未解码的原始查询串,自己解析参数:@GetMapping public void foo(HttpServletRequest request) { String rawQuery = request.getQueryString(); // 自行解析rawQuery,比如按&分割,再按=分割参数键值对 // 此时email参数的原始值就是foo+bar@example.com String email = Arrays.stream(rawQuery.split("&")) .filter(param -> param.startsWith("email=")) .map(param -> param.substring(6)) .findFirst() .orElse(null); System.out.println(email); // 输出foo+bar@example.com }自定义参数解析器
注册一个自定义的RequestParamMethodArgumentResolver,覆盖默认的解码逻辑,让它不对+进行转换。这种方式比较复杂,需要深入Spring的参数解析机制,不建议轻易修改,容易破坏其他参数的正常解析。
三、应该禁用它,还是客户端编码+为%2B?
强烈推荐在客户端对+进行编码为%2B,这是符合HTTP规范的正确做法。
因为在URL查询参数中,+的语义就是空格,如果你要传递字面意义的+,就必须编码为%2B。这样不仅能解决Spring解码的问题,还能保证你的接口在其他客户端(比如Postman、curl)调用时也能正常工作。
对于使用Spring RestTemplate的客户端,正确的编码方式如下:
方式1:使用UriComponentsBuilder(推荐)
UriComponentsBuilder会自动对特殊字符进行规范编码,包括把+转成%2B:
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("http://localhost/foo") .queryParam("email", "foo+bar@example.com") .build(true); // true表示强制编码所有需要转义的字符 ResponseEntity<Void> response = restTemplate.getForEntity(uriComponents.toUri(), Void.class);
方式2:使用UriTemplate
UriTemplate在扩展参数时也会自动编码特殊字符:
UriTemplate template = new UriTemplate("http://localhost/foo?email={email}"); URI uri = template.expand("foo+bar@example.com"); // 此时uri中的email参数会被编码为foo%2Bbar@example.com
总结
- Spring的解码行为是符合HTTP规范的,和请求体类型无关;
- 不建议修改服务器端的解码逻辑,容易破坏规范兼容性;
- 最优方案是在客户端使用Spring提供的URL构建工具,自动将
+编码为%2B,遵循标准。
内容的提问来源于stack exchange,提问作者Pablo Fernandez

