Java Spring RestTemplate post使用HashMap中的参数抛出400个错误请求

Java Spring RestTemplate post使用HashMap中的参数抛出400个错误请求,java,spring,spring-boot,Java,Spring,Spring Boot,当我使用SpringRestTemplate.postForObject使用HashMap发布参数时,服务器抛出400个错误请求: Map<String, Object> uriVariables = new HashMap<>(); uriVariables.put("param1", "param1val"); restTemplate.postForObject(url, uriVariables, responseType); 但是如果我使用多值映射,它就可以工

当我使用Spring
RestTemplate.postForObject
使用
HashMap
发布参数时,服务器抛出400个错误请求:

Map<String, Object> uriVariables = new HashMap<>();
uriVariables.put("param1", "param1val");
restTemplate.postForObject(url, uriVariables, responseType);
但是如果我使用
多值映射
,它就可以工作了

MultiValueMap<String, Object> uriVariables = new LinkedMultiValueMap<>();
uriVariables.add("param1", "param1val");
restTemplate.postForObject(url, uriVariables, responseType);
以下是控制器方法:

@RequestMapping(name = "/test", method = RequestMethod.POST)
@ResponseBody
public String test(@RequestParam("param1") final String param1) {
    // some class Test
    final Test test = new Test();
    test.setName(param1);
    return JsonUtils.toJson(test);
}

有人能解释一下为什么使用HashMap传递请求参数不起作用吗?

当你说:
它抛出400个错误请求:
你明白它所指的是什么吗?提示:不是SpringREST客户机代码,而是您正在与之交谈的服务器,它不接受您的http请求为有效

您可以激活spring restTemplate使用的httpclient实现的日志记录,以查看从
HashMap
LinkedMultiValueMap
如何更改生成的http请求

您还可以查看SpringRESTTemplate源代码,了解为什么生成不同的请求

编辑:您发布的日志的重要部分:

14:51:20.161[main]调试org.springframework.web.client.restemplate-使用[org.springframework.http.converter.json]编写[{param1=1}]。MappingJackson2HttpMessageConverter@8e24743]

在这里,它无疑是在http请求体上编写json

使用LinkedMultiValueMap时:

14:52:08.502[main]调试org.springframework.web.client.restemplate-使用[org.springframework.http.converter.support]编写[{param1=[1]}]。AllEncompassingFormHttpMessageConverter@4abdb505]

在第一种情况下,您的请求不会有参数
param1
,而是请求正文中的所有数据,而在第二种情况下,您会得到一个
param1
参数,使请求对服务器有效

应该可以通过扩展/创建消息转换器来配置
RestTemplate
,以使用
HashMap
,但我建议您继续使用
LinkedMultiValueMap
,原因有二:

  • 这是默认的弹簧设置。因此,当您需要更新到下一个版本时,迁移将更加容易
  • 其他使用您的代码的人不会对RestTemplate处理自定义配置的“新”方式感到惊讶

您可以选择使用
HttpHeaders
HttpEntity
MultiValueMap
hashMap
。您可以查找此关键字和链接。可能有用

这是示例代码:

HttpHeaders headers = new HttpHeaders();

headers.add("x-sample-session-id", sampleService.getServerSessionId());

MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();


map.add("file", new ByteArrayResource(model.getFile().getBytes()));
map.add("apList", model.getApList());

HttpEntity<?> request = new HttpEntity<Object>(map, headers);

String url = sampleService.getContextPath() + "/thirdParty/" + sampleService.getThirdPartyId() + "/child/"
        + childId + "/location?sessionId=" + childSessionId
        + "&op=sendOnDemandLocationResponseWithPhoto&latitude=" + latitude + "&longitude=" + longitude
        + "&datetime=" + datetime + "&provider=" + provider + "&guardianId=" + guardianId + "&cellInfos="
        + cellInfos + "&timestamp=" + timestamp + "&battery=" + battery;

RestTemplate rest = new RestTemplate();

try {
    // success
    return rest.exchange(url, HttpMethod.POST, request, String.class);
} catch (HttpStatusCodeException e) {
    // new Error Response
    return ResponseEntity.status(e.getStatusCode()).headers(e.getResponseHeaders())
            .body(e.getResponseBodyAsString());
}
HttpHeaders=newhttpheaders();
headers.add(“x-sample-session-id”,sampleService.getServerSessionId());
MultiValueMap=新链接的MultiValueMap();
add(“文件”,新的ByteArrayResource(model.getFile().getBytes());
add(“apList”,model.getApList());
HttpEntity请求=新的HttpEntity(映射、头);
字符串url=sampleService.getContextPath()+“/thirdParty/”+sampleService.getThirdPartyId()+“/child/”
+childId+“/location?sessionId=“+childSessionId”
+“&op=sendOnDemandLocationResponseWithPhoto&latitude=“+纬度+”&经度=“+经度
+“&datetime=“+datetime+”&provider=“+provider+”&guardianId=“+guardianId+”&cellInfos=”
+cellInfos+“×tamp=“+timestamp+”&battery=“+battery;
RestTemplate rest=新建RestTemplate();
试一试{
//成功
返回rest.exchange(url,HttpMethod.POST,request,String.class);
}捕获(HttpStatusCodeException){
//新的错误响应
返回ResponseEntity.status(如getStatusCode()).headers(如getResponseHeaders())
.body(例如getResponseBodyAsString());
}

要使用HashMap使其正常工作,请尝试以下方法:-

List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
AllEncompassingFormHttpMessageConverter
扩展了
FormHttpMessageConverter
,它实现了
HttpMessageConverter
这里需要注意
多值映射


而在
MappingJackson2HttpMessageConverter
的情况下,它扩展了
AbstractJackson2HttpMessageConverter
,它进一步扩展了
AbstractGenericHttpMessageConverter
注意
对象
多值映射的值类型与HashMap不同。可以在其接口定义中看到:

interface MultiValueMap<K, V> extends Map<K, List<V>>
接口多值映射扩展映射
和HashMap:

class HashMap<K,V> extends AbstractMap<K,V>
类HashMap扩展了AbstractMap
您使用
put
方法填充HashMap,使用
add
方法填充MultiValueMap,因此它看起来很相似,但实际上MultiValueMap包含列表,而不是单个值。可以在您的日志中看到:

哈希映射:

14:51:20.161[main]调试org.springframework.web.client.RestTemplate -写入[{param1=1}]

多值映射:

14:52:08.502[main]调试org.springframework.web.client.RestTemplate -写入[{param1=}]

如您所见,
{param1=1}
不起作用,
{param1=[1]}
起作用

此外,在
RestTemplate
中,建议使用多值映射:

实体的主体或请求本身可以是到的多值映射 创建一个多部分请求。多值映射中的值可以是任意值 表示零件主体的对象,或HttpEntity 用主体和标题表示零件。多值映射可以是 使用MultipartBodyBuilder方便地构建


第二个代码段错误且未编译-
MultiValueMap::put
方法接受值列表,而不是单个值:
公共列表put(K键,列表值)
。你弄错了吗?对不起,是打字错误。谢谢你的回答。有没有办法让
HashMap
处理post请求,因为
MultiValueMap
是针对单个参数的多个值,我不需要。我只想针对单个参数传递单个值。否则,我将在整个系统中使用
多值映射
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
this.messageConverters.add(new SourceHttpMessageConverter<>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (jackson2Present) {
    this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
interface MultiValueMap<K, V> extends Map<K, List<V>>
class HashMap<K,V> extends AbstractMap<K,V>