1. 概述

本文将系统性地介绍 Spring 提供的 REST 客户端工具 RestTemplate,涵盖其在各类常见 HTTP 操作中的实际应用场景与最佳实践。

所有示例均基于一个本地运行的 REST 服务,源码可参考:https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-resttemplate

RestTemplate 虽然在新项目中逐渐被 WebClient 取代,但在大量存量系统中仍是核心通信组件,掌握其用法仍是 Java 开发者的必备技能。


2. 使用 GET 获取资源

2.1 获取原始 JSON 响应

最基础的 GET 请求可以使用 getForEntity() 方法,它返回完整的 ResponseEntity,包含状态码、响应头和响应体。

✅ 示例:获取单个 Foo 资源

RestTemplate restTemplate = new RestTemplate();
String fooResourceUrl = "http://localhost:8080/spring-rest/foos";

ResponseEntity<String> response = restTemplate.getForEntity(fooResourceUrl + "/1", String.class);

// 验证响应状态
Assertions.assertEquals(response.getStatusCode(), HttpStatus.OK);

⚠️ 注意:getForEntity() 返回的是包装对象,便于我们访问 HTTP 元信息。

接下来可以使用 Jackson 解析返回的 JSON 字符串:

ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.getBody());
JsonNode name = root.path("name");

Assertions.assertNotNull(name.asText());

这种写法适合你只需要部分字段,或 DTO 尚未定义的场景。


2.2 直接映射为 POJO

更常见的做法是直接将响应体反序列化为 Java 对象。

先定义资源 DTO:

public class Foo implements Serializable {
    private long id;
    private String name;
    
    // 标准 getter/setter 省略
}

然后使用 getForObject(),简洁粗暴:

Foo foo = restTemplate.getForObject(fooResourceUrl + "/1", Foo.class);

Assertions.assertNotNull(foo.getName());
Assertions.assertEquals(foo.getId(), 1L);

❌ 缺点:无法获取响应头或状态码。如果需要这些信息,还是得用 getForEntity()


3. 使用 HEAD 获取响应头

HEAD 请求不返回响应体,仅获取元信息,适合做资源存在性检查或获取内容类型。

✅ 示例:检查接口返回的 Content-Type

HttpHeaders httpHeaders = restTemplate.headForHeaders(fooResourceUrl);

// 验证是否支持 JSON
Assertions.assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));

这个方法在做健康检查或预检时非常有用,避免传输不必要的数据。


4. 使用 POST 创建资源

创建资源主要依赖三个方法:

  • postForLocation() → 返回新资源的 URI
  • postForObject() → 返回创建后的资源对象
  • exchange() → 最灵活,可自定义任意参数

4.1 postForObject()

直接获取创建后的对象,适合需要立即使用返回数据的场景:

RestTemplate restTemplate = new RestTemplate();

HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
Foo foo = restTemplate.postForObject(fooResourceUrl, request, Foo.class);

Assertions.assertNotNull(foo);
Assertions.assertEquals(foo.getName(), "bar");

⚠️ 注意:postForObject() 内部会从 Location 头自动发起一次 GET 请求获取资源,因此会产生两次调用。


4.2 postForLocation()

只关心新资源的地址,不立即获取内容:

HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
URI location = restTemplate.postForLocation(fooResourceUrl, request);

Assertions.assertNotNull(location);
// location 示例: http://localhost:8080/spring-rest/foos/123

✅ 适用于异步创建或后续自行处理的场景,避免额外开销。


4.3 exchange()

最通用的 API,支持所有 HTTP 方法和自定义配置:

RestTemplate restTemplate = new RestTemplate();
HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));

ResponseEntity<Foo> response = restTemplate
    .exchange(fooResourceUrl, HttpMethod.POST, request, Foo.class);

Assertions.assertEquals(response.getStatusCode(), HttpStatus.CREATED);
Foo foo = response.getBody();

Assertions.assertNotNull(foo);
Assertions.assertEquals(foo.getName(), "bar");

✅ 推荐在复杂请求中使用,控制力最强。


4.4 提交表单数据

提交 application/x-www-form-urlencoded 类型的表单数据,需注意:

  1. 设置正确的 Content-Type
  2. 使用 MultiValueMap 包装参数

✅ 示例代码:

// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

// 构造表单参数
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("id", "1");
map.add("name", "test");

// 构建请求实体
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

// 发送请求
ResponseEntity<String> response = restTemplate.postForEntity(
    fooResourceUrl + "/form", request, String.class);

Assertions.assertEquals(response.getStatusCode(), HttpStatus.CREATED);

⚠️ 踩坑提醒:忘记设置 Content-Type 会导致后端无法正确解析参数。


5. 使用 OPTIONS 获取允许的操作

通过 OPTIONS 请求可获取某个接口支持的 HTTP 方法列表。

Set<HttpMethod> allowedMethods = restTemplate.optionsForAllow(fooResourceUrl);

HttpMethod[] expected = {HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE};
Assertions.assertTrue(allowedMethods.containsAll(Arrays.asList(expected)));

✅ 实际用途:

  • 动态构建客户端行为
  • 权限探测
  • API 文档自动生成

6. 使用 PUT 更新资源

RestTemplateput() 方法不返回响应体,若需获取状态码或头信息,必须使用 exchange()


6.1 使用 exchange() 发起简单 PUT

Foo updatedInstance = new Foo("newName");
updatedInstance.setId(createResponse.getBody().getId());

String resourceUrl = fooResourceUrl + '/' + createResponse.getBody().getId();
HttpEntity<Foo> requestUpdate = new HttpEntity<>(updatedInstance, headers);

// PUT 通常无返回体,使用 Void.class
template.exchange(resourceUrl, HttpMethod.PUT, requestUpdate, Void.class);

✅ 注意:PUT 是幂等操作,适合全量更新。


6.2 使用 RequestCallback 自定义请求

execute() 方法配合 RequestCallback 可精细控制请求过程,比如手动序列化或添加动态头。

RequestCallback requestCallback(final Foo updatedInstance) {
    return clientHttpRequest -> {
        ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(clientHttpRequest.getBody(), updatedInstance);
        
        clientHttpRequest.getHeaders().add(
            HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        clientHttpRequest.getHeaders().add(
            HttpHeaders.AUTHORIZATION, "Basic " + getBase64EncodedLogPass("admin", "secret"));
    };
}

// 先创建资源
ResponseEntity<Foo> response = restTemplate
    .exchange(fooResourceUrl, HttpMethod.POST, request, Foo.class);
Assertions.assertEquals(response.getStatusCode(), HttpStatus.CREATED);

// 再更新
Foo updatedInstance = new Foo("newName");
updatedInstance.setId(response.getBody().getId());
String resourceUrl = fooResourceUrl + '/' + response.getBody().getId();

restTemplate.execute(
    resourceUrl, 
    HttpMethod.PUT, 
    requestCallback(updatedInstance), 
    clientHttpResponse -> null);

⚠️ getBase64EncodedLogPass("admin", "secret") 返回 YWRtaW46c2VjcmV0,模拟 Basic Auth。


7. 使用 DELETE 删除资源

DELETE 操作最简单,无返回值:

String entityUrl = fooResourceUrl + "/" + existingResource.getId();
restTemplate.delete(entityUrl);

✅ 通常配合 204 No Content 使用,表示删除成功但无内容返回。


8. 超时配置

RestTemplate 默认无超时,生产环境必须显式配置,否则可能造成连接堆积。

四种关键超时类型:

类型 说明 配置位置
Connect Timeout 建立 TCP 连接超时 RequestConfig
Read Timeout 读取服务器响应超时 RequestConfig
Socket Timeout SSL 握手或 CONNECT 超时 SocketConfig
Connection Request Timeout 从连接池获取连接超时 RequestConfig

8.1 使用 HttpComponentsClientHttpRequestFactory

简单配置方式:

RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());

ClientHttpRequestFactory getClientHttpRequestFactory() {
    int timeout = 5;
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    factory.setConnectionRequestTimeout(timeout * 1000);
    factory.setConnectTimeout(timeout * 2000);
    factory.setReadTimeout(timeout * 3000);
    return factory;
}

8.2 使用 HttpClient 精细化配置

更推荐的方式,利用 Apache HttpClient 的完整能力:

ClientHttpRequestFactory getClientHttpRequestFactoryAlternate() {
    long timeout = 5; 
    int readTimeout = 5;

    // 连接建立超时
    ConnectionConfig connectionConfig = ConnectionConfig.custom()
        .setConnectTimeout(Timeout.ofMilliseconds(timeout * 1000))
        .build();
    
    // 从连接池获取连接超时
    RequestConfig requestConfig = RequestConfig.custom()
        .setConnectionRequestTimeout(Timeout.ofMilliseconds(timeout * 2000))
        .build();

    // Socket 读写超时
    SocketConfig socketConfig = SocketConfig.custom() 
        .setSoTimeout(Timeout.ofMilliseconds(timeout * 1000)).build();

    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
    connectionManager.setDefaultSocketConfig(socketConfig);
    connectionManager.setDefaultConnectionConfig(connectionConfig);    

    CloseableHttpClient httpClient = HttpClientBuilder.create()
        .setConnectionManager(connectionManager)
        .setDefaultRequestConfig(requestConfig)
        .build();

    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
    factory.setReadTimeout(readTimeout * 3000);
  
    return factory;
}

✅ 建议:生产环境务必配置所有超时,避免雪崩。


9. 总结

本文覆盖了 RestTemplate 的核心使用场景:

  • ✅ GET:getForObject / getForEntity
  • ✅ POST:postForLocation / postForObject / exchange
  • ✅ PUT / DELETE / HEAD / OPTIONS:各司其职
  • ✅ 表单提交、超时配置等实战细节

虽然 RestTemplate 已进入维护模式(Spring 5+ 推荐使用 WebClient),但在传统项目中仍广泛使用。

如需了解认证相关实践,可参考:Spring 中使用 RestTemplate 配置 Basic Auth


原始标题:A Guide to the RestTemplate | Baeldung

« 上一篇: Java Web周报38
» 下一篇: Java Web每周39