强大又优雅!Spring Boot 中 RestTemplate 的最佳实践详解
在现代开发中,API 的设计和调用变得尤为重要,尤其是基于 REST 架构的服务调用。RestTemplate
是 Spring 提供的用于同步调用 RESTful 服务的强大工具,它支持各种 HTTP 方法,如 GET、POST、PUT、DELETE 等。作为开发者,理解并掌握如何高效使用 RestTemplate
是优化服务交互性能的重要一步。本文旨在深入探讨 RestTemplate
的 POST 请求方法以及 exchange()
和 execute()
等常用方法,帮助你在实际开发中灵活使用这些工具应对复杂的业务场景。
通过示例代码,我们将详细演示如何使用 RestTemplate
进行 POST 请求,包括如何设置请求头和请求体、如何构建和传递复杂数据,以及如何处理返回的响应。同时,我们还将探索如何使用 exchange()
指定 HTTP 方法,实现灵活的请求处理。无论是初学者还是有经验的开发者,这篇文章都将为你提供有价值的参考。
RestTemplate
是 Spring
提供的用于访问 REST 服务的客户端。
它提供了多种便捷的方法来访问远程 HTTP 服务,大大提高了客户端代码开发的效率。
之前,我使用 Apache 的 HttpClient 开发 HTTP 请求。代码非常复杂,我不得不管理资源清理等问题,代码繁琐且包含大量冗余部分。
以下是我封装的一个 post 请求工具的截图:
本教程将指导你如何在 Spring 生态系统中使用 RestTemplate
进行 GET
和 POST
请求,并通过 exchange
方法来指定请求类型。同时,还会分析 RestTemplate
的核心方法。
阅读完本教程后,你将能够以优雅的方式进行 HTTP 请求。
RestTemplate
简介
*RestTemplate*
是*Spring*
中用于同步客户端通信的核心类。它简化了与HTTP
服务的通信,并遵循RestFul
原则。代码只需提供一个 URL 并提取结果。默认情况下,
RestTemplate
依赖于 JDK 的 HTTP 连接工具。但是,你可以通过setRequestFactory
属性切换到其他 HTTP 工具源,例如Apache HttpComponents
、Netty
和OkHttp
。
RestTemplate
大大简化了表单数据的提交,并包含对 JSON 数据的自动转换。
然而,要真正掌握它的使用,必须理解 HttpEntity 的结构(包括 headers 和 body)以及它与 uriVariables 之间的区别。
这一点在 POST 请求中尤为明显,稍后我们将详细讨论。
此类的主要入口点基于六种 HTTP 方法:
此外,exchange
和 execute
可以与上述方法互换使用。
在内部,RestTemplate
默认使用 HttpMessageConverter
实例,将 HTTP
消息转换为 POJO
或将 POJO
转换为 HTTP
消息。默认情况下,它会为主要的 MIME 类型注册转换器,但你也可以通过 setMessageConverters
注册其他转换器。
(在实际使用中,这一点并不十分明显;许多方法都有一个 responseType 参数,你可以传递一个与响应体映射的对象,底层的 HttpMessageConverter 会进行映射。)
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
HttpMessageConverter.java
源代码:
public interface HttpMessageConverter<T> {
// 判断该转换器是否可以读取给定的类。
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
// 判断该转换器是否可以写入给定的类。
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
// 返回一个 List<MediaType>
List<MediaType> getSupportedMediaTypes();
// 读取输入消息
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// 将对象写入输出消息
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
在内部,RestTemplate
默认使用 SimpleClientHttpRequestFactory
和 DefaultResponseErrorHandler
来分别处理 HTTP
请求的创建和错误处理。
然而,你可以通过 setRequestFactory
和 setErrorHandler
来覆盖这些默认行为。
GET 请求
getForObject()
方法
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables){}
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
public <T> T getForObject(URI url, Class<T> responseType)
getForObject()
方法可以将 HTTP 响应直接转换为 POJO,而不像 getForEntity()
需要额外处理响应。
getForObject
会直接返回 POJO,省略了大量响应信息。
POJO 示例:
public class Notice {
private int status;
private Object msg;
private List<DataBean> data;
}
public class DataBean {
private int noticeId;
private String noticeTitle;
private Object noticeImg;
private long noticeCreateTime;
private long noticeUpdateTime;
private String noticeContent;
}
无参数的 GET 请求
/**
* 无参数的 GET 请求
*/
@Test
public void restTemplateGetTest(){
RestTemplate restTemplate = new RestTemplate();
Notice notice = restTemplate.getForObject("http://icoderoad.com/notice/list/1/5", Notice.class);
System.out.println(notice);
}
控制台输出:
INFO 19076 --- [ main] c.w.s.c.w.c.HelloControllerTest
: Started HelloControllerTest in 5.532 seconds (JVM running for 7.233)
Notice{status=200, msg=null, data=[DataBean{noticeId=21, noticeTitle='aaa', noticeImg=null,
noticeCreateTime=1525292723000, noticeUpdateTime=1525292723000, noticeContent='<p>aaa</p>'},
DataBean{noticeId=20, noticeTitle='ahaha', noticeImg=null, noticeCreateTime=1525291492000,
noticeUpdateTime=1525291492000, noticeContent='<p>ah.......'
带参数的 GET 请求 1
Notice notice = restTemplate.getForObject("http://icoderoad.com/notice/list/{1}/{2}", Notice.class, 1, 5);
可以看到,使用了占位符 {1}
和 {2}
。
带参数的 GET 请求 2
Map<String, String> map = new HashMap<>();
map.put("start", "1");
map.put("page", "5");
Notice notice = restTemplate.getForObject("http://icoderoad.com/notice/list/", Notice.class, map);
在这种情况下,使用 Map
加载参数,默认情况下会以 PathVariable
格式解析 URL。
getForEntity()
方法
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables){}
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables){}
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType){}
与 getForObject()
不同,此方法返回 ResponseEntity
对象。
如果需要将其转换为 POJO,则必须使用 JSON 工具类,可根据个人偏好选择。
对于不熟悉解析 JSON 的人,可以查阅工具如 FastJson
或 Jackson
。接下来我们来探讨 ResponseEntity
的相关方法。
ResponseEntity、HttpStatus 和 BodyBuilder 的结构
//ResponseEntity.java
public HttpStatus getStatusCode(){}
public int getStatusCodeValue(){}
public boolean equals(@Nullable Object other) {}
public String toString() {}
public static BodyBuilder status(HttpStatus status) {}
public static BodyBuilder ok() {}
public static <T> ResponseEntity<T> ok(T body) {}
public static BodyBuilder created(URI location) {}
...
//HttpStatus.java
public enum HttpStatus {
public boolean is1xxInformational() {}
public boolean is2xxSuccessful() {}
public boolean is3xxRedirection() {}
public boolean is4xxClientError() {}
public boolean is5xxServerError() {}
public boolean isError() {}
}
//BodyBuilder.java
public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
// 通过 Content-Length 头设置响应实体的内容长度
BodyBuilder contentLength(long contentLength);
// 设置响应实体的 MediaType
BodyBuilder contentType(MediaType contentType);
// 设置响应实体的内容并返回
<T> ResponseEntity<T> body(@Nullable T body);
}
如上所示,ResponseEntity
包含了来自 HttpStatus
和 BodyBuilder
的信息,这使得处理原始响应变得更加简单。
示例:
@Test
public void rtGetEntity(){
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Notice> entity = restTemplate.getForEntity("http://icoderoad.com/notice/list/1/5", Notice.class);
HttpStatus statusCode = entity.getStatusCode();
System.out.println("statusCode.is2xxSuccessful()" + statusCode.is2xxSuccessful());
Notice body = entity.getBody();
System.out.println("entity.getBody()" + body);
ResponseEntity.BodyBuilder status = ResponseEntity.status(statusCode);
status.contentLength(100);
status.body("在此处添加一个声明");
ResponseEntity<Class<Notice>> body1 = status.body(Notice.class);
Class<Notice> body2 = body1.getBody();
System.out.println("body1.toString()" + body1.toString());
}
输出:
statusCode.is2xxSuccessful() true
entity.getBody() Notice{status=200, msg=null, data=[DataBean{noticeId=21, noticeTitle='aaa', ...
body1.toString() <200 OK, class com.waylau.spring.cloud.weather.pojo.Notice, {Content-Length=[100]}>
当然,像 getHeaders()
这样的方法也是可用的,但在此不作示例。
POST 请求
类似于 GET 请求,POST 请求也有
postForObject
和postForEntity
方法。
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException {}
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException {}
public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException {}
示例
这里我使用一个邮箱验证接口进行测试。
@Test
public void rtPostObject(){
RestTemplate restTemplate = new RestTemplate();
String url = "http://47.xxx.xxx.96/register/checkEmail";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("email", "844072586@qq.com");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
System.out.println(response.getBody());
}
执行结果:
{"status":500,"msg":"该邮箱已经注册","data":null}
为什么使用 MultiValueMap
?
MultiValueMap
是 Map
的子类,它允许每个键存储多个值。这里简单介绍该接口:
public interface MultiValueMap<K, V> extends Map<K, List<V>> {...}
使用 MultiValueMap
的原因是 HttpEntity
接受的请求类型为 MultiValueMap
:
public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers){}
在这个构造函数中,我们传入的 map
是请求体,headers
是请求头。
我们使用 HttpEntity
是因为,尽管 restTemplate.postForEntity
方法似乎接受 @Nullable Object request
类型,但如果深入追溯,会发现这个 request
是通过 HttpEntity
解析的。核心代码如下:
if (requestBody instanceof HttpEntity) {
this.requestEntity = (HttpEntity<?>) requestBody;
} else if (requestBody != null) {
this.requestEntity = new HttpEntity<>(requestBody);
} else {
this.requestEntity = HttpEntity.EMPTY;
}
我曾尝试使用 map
传递参数,虽然编译时没有报错,但请求无效,最终出现 400 错误。
这种请求方式已经满足 POST 请求的需求,同时,cookie 作为请求头的一部分,也可以根据需要进行设置。
其他方法与此类似。
使用 exchange
指定 HTTP 方法
exchange()
方法与 getForObject()
、getForEntity()
、postForObject()
和 postForEntity()
不同之处在于它允许你指定 HTTP 方法。
你会注意到,所有的 exchange
方法似乎都有 @Nullable HttpEntity<?> requestEntity
参数,这意味着我们需要使用 HttpEntity
来传递请求体。正如前面提到的,使用 HttpEntity
性能更好。
示例
@Test
public void rtExchangeTest() throws JSONException {
RestTemplate restTemplate = new RestTemplate();
String url = "http://icoderoad.com/notice/list";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
JSONObject jsonObj = new JSONObject();
jsonObj.put("start", 1);
jsonObj.put("page", 5);
HttpEntity<String> entity = new HttpEntity<>(jsonObj.toString(), headers);
ResponseEntity<JSONObject> exchange = restTemplate.exchange(url, HttpMethod.GET, entity, JSONObject.class);
System.out.println(exchange.getBody());
}
这次我使用了 JSONObject
来传递和返回数据。其他 HttpMethod
方法的使用类似。
使用 execute
指定 HTTP 方法
execute()
方法类似于 exchange()
,它允许你指定不同的 HttpMethod
类型。但不同之处在于它返回的响应体是一个对象 <T>
,而不是 ResponseEntity<T>
。
需要强调的是,execute()
方法是上述所有方法的底层实现。以下是一个示例:
@Override
@Nullable
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
结语
通过对 RestTemplate
不同方法的深入讲解,特别是 POST 请求的使用以及 exchange()
、execute()
的灵活运用,本文展示了在开发过程中如何使用这些方法简化与外部服务的交互,并提升代码的可读性和维护性。在面对复杂的业务需求时,掌握这些技术将帮助开发者在请求数据、处理响应以及提升 API 性能方面取得更好的平衡。
对于高效的 HTTP 请求处理,RestTemplate
提供了丰富的功能,灵活支持多种请求方式和参数配置,极大地简化了开发流程。随着项目复杂度的增加,理解和掌握这些工具的使用技巧,能够大大提升开发效率,同时减少潜在的错误。通过深入研究 RestTemplate
,我们可以构建出更加健壮、高效的服务交互机制,满足不断变化的业务需求。希望本文能为你提供深入的理解,助力你的开发之旅。