Spring RestTemplate带参数的GET请求

390

我需要进行一个包含自定义头信息和查询参数的 REST 调用。我只设置了带有头信息(没有主体)的 HttpEntity,并使用以下方式的 RestTemplate.exchange() 方法:

HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "application/json");

Map<String, String> params = new HashMap<String, String>();
params.put("msisdn", msisdn);
params.put("email", email);
params.put("clientVersion", clientVersion);
params.put("clientType", clientType);
params.put("issuerName", issuerName);
params.put("applicationName", applicationName);

HttpEntity entity = new HttpEntity(headers);

HttpEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class, params);

使用调度程序servlet无法解析请求到处理程序,导致客户端失败。通过调试,发现似乎没有发送请求参数。

当我使用一个请求正文和没有查询参数的POST进行交换时,它可以正常工作。

有人有任何想法吗?

17个回答

680

为了轻松操作URL、路径、参数等,您可以使用Spring的UriComponentsBuilder类创建一个带有参数占位符的URL模板,然后在RestOperations.exchange(...)调用中提供这些参数的值。这比手动拼接字符串更简洁,并且它会自动处理URL编码:

HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
HttpEntity<?> entity = new HttpEntity<>(headers);

String urlTemplate = UriComponentsBuilder.fromHttpUrl(url)
        .queryParam("msisdn", "{msisdn}")
        .queryParam("email", "{email}")
        .queryParam("clientVersion", "{clientVersion}")
        .queryParam("clientType", "{clientType}")
        .queryParam("issuerName", "{issuerName}")
        .queryParam("applicationName", "{applicationName}")
        .encode()
        .toUriString();

Map<String, ?> params = new HashMap<>();
params.put("msisdn", msisdn);
params.put("email", email);
params.put("clientVersion", clientVersion);
params.put("clientType", clientType);
params.put("issuerName", issuerName);
params.put("applicationName", applicationName);

HttpEntity<String> response = restOperations.exchange(
        urlTemplate,
        HttpMethod.GET,
        entity,
        String.class,
        params
);

16
好的,我会尽力进行翻译。将原先的 exchange 替换成 getForEntity 是一个很好的建议,可以让代码更简洁:restTemplate.getForEntity(builder.build().encode().toUri(), String.class); - Fernando M. Pinheiro
14
您是正确的,但如果您希望在响应中得到通用类型,则需要使用exchange并提供ParameterizedTypeReference。此示例可以更进一步地简化,将builder.build().encode().toUri()替换为builder.toUriString() - mirzmaster
4
获取URI的捷径是调用builder.toUriString() - Michael Piefel
13
注意!如果你这样做,会出现双重编码的问题。在调用 builder.toUriString() 时,"some&value" 会被编码为 "some%26value",然后在 'exchange()' 中再次编码为 "some%2526value"。最好使用 uriVariables 来传递参数。 - steve
5
如@steve所说,如果您按照此答案中的方法进行操作(包括在参数值中有空格的情况),则可能会导致双重编码的问题。如果您使用.build().toUriString()而不是简单的.toUriString(),则可以解决此问题。这样可以跳过调用.encode()来解决问题。请参见https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/UriComponentsBuilder.html#toUriString--。 - riverhorse
显示剩余8条评论

263

uriVariables 也会被扩展到查询字符串中。例如,以下调用将扩展账户和名称的值:

restTemplate.exchange("http://my-rest-url.org/rest/account/{account}?name={name}",
    HttpMethod.GET,
    httpEntity,
    clazz,
    "my-account",
    "my-name"
);

因此,实际请求的URL将是:

http://my-rest-url.org/rest/account/my-account?name=my-name

查看HierarchicalUriComponents.expandInternal(UriTemplateVariables)以获取更多详细信息。Spring的版本为3.1.3。


谢谢 - 非常简单的解决方案 - Angshuman Agarwal
4
创建RestTemplate实例时,可以通过指定DefaultUriTemplateHandler(Spring 5之前)或DefaultUriBuilderFactory(Spring 5+)来确定如何展开查询参数值。这在您希望对额外字符进行编码(例如!,(),等)时非常有用。 - Stephen Rudolph
我的URL有10多个参数,有没有办法使用对象/映射来实现相同的效果,而不是列出每个变量?我也不能使用UriComponentsBuilder,因为它会导致Micrometer为每个请求生成不同的指标。 - Doug
2
@Doug — RestTemplate有并行方法,可以指定一个位置数组的值(Object... uriVariables)或者一个命名值的映射(Map<String, ?> uriVariables)。听起来像是你想要使用映射版本:restTemplate.exchange(url, HttpMethod.GET, httpEntity, clazz, urlVariablesMap) - M. Justin
确切地说! 您可以通过调用以下方式检查URL结果: restTemplate.getUriTemplateHandler().expand(“/some/{some}/{other}”, some, other);请参阅org.springframework.web.util.UriTemplateHandler。 - Denis Orlov

69
自至少Spring 3以来,不再使用UriComponentsBuilder构建URL(有点冗长),许多RestTemplate方法接受路径中的占位符作为参数(不仅限于exchange)。
从文档中可以看到:

Many of the RestTemplate methods accepts a URI template and URI template variables, either as a String vararg, or as Map<String,String>.

For example with a String vararg:

restTemplate.getForObject(
   "http://example.com/hotels/{hotel}/rooms/{room}", String.class, "42", "21");

Or with a Map<String, String>:

Map<String, String> vars = new HashMap<>();
vars.put("hotel", "42");
vars.put("room", "21");

restTemplate.getForObject("http://example.com/hotels/{hotel}/rooms/{room}", 
    String.class, vars);

参考资料: https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#rest-resttemplate-uri

如果您查看RestTemplateJavaDoc并搜索"URI模板",您可以看到可以使用占位符的方法。


33
    String uri = http://my-rest-url.org/rest/account/{account};

    Map<String, String> uriParam = new HashMap<>();
    uriParam.put("account", "my_account");

    UriComponents builder = UriComponentsBuilder.fromHttpUrl(uri)
                .queryParam("pageSize","2")
                        .queryParam("page","0")
                        .queryParam("name","my_name").build();

    HttpEntity<String> requestEntity = new HttpEntity<>(null, getHeaders());

    ResponseEntity<String> strResponse = restTemplate.exchange(builder.toUriString(),HttpMethod.GET, requestEntity,
                        String.class,uriParam);

    //final URL: http://my-rest-url.org/rest/account/my_account?pageSize=2&page=0&name=my_name

RestTemplate: 使用UriComponents构建动态URI(URI变量和请求参数)


31

好的,我犯了一个愚蠢的错误,混淆了查询参数和 URL 参数。我希望有一种更好的方法来填充我的查询参数而不是使用一个丑陋的拼接字符串,但现在只能这样做。简单地说,这只是构建具有正确参数的 URL 的问题。如果将其作为字符串传递,Spring 也会负责对其进行编码。


你试过了吗?我按照使用UriComponentsBuilder的相同方法,但是在目标URL上,当我执行request.getAttribute()时,返回null。 - yathirigan
85
我真的不明白为什么这个答案有绿色勾选标志。 - Pradeep
16
因为他是 OP。 - Kalpesh Soni
那么你的解决方案是什么?谢谢! - Raymond Chen

21

我试图做类似的事情,而 RoboSpice示例 帮助我解决了这个问题:

HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "application/json");

HttpEntity<String> request = new HttpEntity<>(input, createHeader());

String url = "http://awesomesite.org";
Uri.Builder uriBuilder = Uri.parse(url).buildUpon();
uriBuilder.appendQueryParameter(key, value);
uriBuilder.appendQueryParameter(key, value);
...

String url = uriBuilder.build().toString();

HttpEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, request , String.class);

17

将哈希映射转换为查询参数字符串:

Map<String, String> params = new HashMap<>();
params.put("msisdn", msisdn);
params.put("email", email);
params.put("clientVersion", clientVersion);
params.put("clientType", clientType);
params.put("issuerName", issuerName);
params.put("applicationName", applicationName);

UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
    builder.queryParam(entry.getKey(), entry.getValue());
}

HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "application/json");

HttpEntity<String> response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, new HttpEntity(headers), String.class);

11
在Spring Web 4.3.6中,我还看到了以下内容:
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)

这意味着您不必创建丑陋的地图。因此,如果您有这个URL:
http://my-url/action?param1={param1}&param2={param2}

您可以选择执行以下操作:

restTemplate.getForObject(url, Response.class, param1, param2)

或者

restTemplate.getForObject(url, Response.class, param [])

5

我采用不同的方法,您可能会赞同或不赞同,但我想从.properties文件中控制,而不是编译后的Java代码

在application.properties文件内部

endpoint.url = https://yourHost/resource?requestParam1={0}&requestParam2={1}

Java代码在这里,您可以编写if或switch条件来查找.properties文件中的端点URL是否具有@PathVariable(包含{})或@RequestParam(yourURL?key=value)等...然后相应地调用方法...这样它就是动态的,未来不需要编码更改,一站式服务...

我试图给出更多的思路而不是实际的代码...尝试为@RequestParam和@PathVariable等编写通用方法...然后在需要时调用即可。

  @Value("${endpoint.url}")
  private String endpointURL;
  // you can use variable args feature in Java
  public String requestParamMethodNameHere(String value1, String value2) {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate
           .getMessageConverters()
           .add(new MappingJackson2HttpMessageConverter());

    HttpHeaders headers = new HttpHeaders();
    headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
    HttpEntity<String> entity = new HttpEntity<>(headers);

    try {
      String formatted_URL = MessageFormat.format(endpointURL, value1, value2);
      ResponseEntity<String> response = restTemplate.exchange(
                    formatted_URL ,
                    HttpMethod.GET,
                    entity,
                    String.class);
     return response.getBody();
    } catch (Exception e) { e.printStackTrace(); }

4
如果您为RestTemplate传递非参数化参数,则对于每个不同的URL,将为所有人都有一个指标,考虑参数。 您应该使用参数化URL:
http://my-url/action?param1={param1}&param2={param2}

替代

http://my-url/action?param1=XXXX&param2=YYYY

第二种情况是使用UriComponentsBuilder类得到的。
实现第一种行为的一种方法如下:
Map<String, Object> params = new HashMap<>();
params.put("param1", "XXXX");
params.put("param2", "YYYY");

String url = "http://my-url/action?%s";

String parametrizedArgs = params.keySet().stream().map(k ->
    String.format("%s={%s}", k, k)
).collect(Collectors.joining("&"));

HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<String> entity = new HttpEntity<>(headers);

restTemplate.exchange(String.format(url, parametrizedArgs), HttpMethod.GET, entity, String.class, params);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接