强大又优雅!Spring Boot 中 RestTemplate 的最佳实践详解

科技   2024-11-06 07:30   河北  


强大又优雅!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 HttpComponentsNetty 和 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,我们可以构建出更加健壮、高效的服务交互机制,满足不断变化的业务需求。希望本文能为你提供深入的理解,助力你的开发之旅。


今天就讲到这里,如果有问题需要咨询,大家可以直接留言或扫下方二维码来知识星球找我,我们会尽力为你解答。


AI资源聚合站已经正式上线,该平台不仅仅是一个AI资源聚合站,更是一个为追求知识深度和广度的人们打造的智慧聚集地。通过访问 AI 资源聚合网站 https://ai-ziyuan.techwisdom.cn/,你将进入一个全方位涵盖人工智能和语言模型领域的宝藏库


作者:路条编程(转载请获本公众号授权,并注明作者与出处)

路条编程
路条编程是一个友好的社区,在这里你可以免费学习编程技能,我们旨在激励想学编程的人尝试新的想法和技术,在最短的时间学习到工作中使用到的技术!
 最新文章