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()
→ 返回新资源的 URIpostForObject()
→ 返回创建后的资源对象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
类型的表单数据,需注意:
- 设置正确的
Content-Type
- 使用
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 更新资源
RestTemplate
的 put()
方法不返回响应体,若需获取状态码或头信息,必须使用 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