Spring 6.1 中的新 RestClient
Spring中的HTTP客户端简介
Spring框架提供了两种不同的选项来执行http请求:
- RestTemplate:它是十多年前在 Spring 3 中引入的。它是提供同步阻塞通信的模板模式的实现。
- WebClient:它在 Spring 5 中作为 Spring WebFlux 库的一部分发布。它提供流畅的 API 并遵循反应式模型。
RestRemplate 方法暴露了太多的 HTTP 功能,导致大量方法重载。它采用 Jakarta Servlet API 中的每个请求一个线程范例。
WebClient 是 RestTemplate 的替代品,支持同步和异步调用。它是 Spring Web Reactive 项目的一部分。
现在 Spring 6.1 M1 版本提供了 RestClient。一个新的同步 http 客户端,其工作方式与 WebClient 类似,使用与 RestTemplate 相同的基础设施。
设置项目
我们将使用 Spring Boot 3.2 和 Spring Web 依赖项。您可以进入 Spring Initializr 页面并选择 Spring Web 依赖项生成一个新项目。使用 maven,pom.xml 将包含
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
就是这样。根本不需要 Spring Reactive Web 依赖。
准备项目
由于这是一个熟悉 RestClient 的简单项目,因此我们将对之前文章中的客户 Web 服务进行 http 调用。此外,嵌入式 tomcat 将被禁用,因为不需要运行 Web 容器。为此,application.properties 文件将包含该属性
spring.main.web-application-type=none
然后,CommandLineRunner 类将完成所有工作。类的基本结构如下所示
@Configuration
public class Initializer implements CommandLineRunner {
private Logger logger =
LoggerFactory.getLogger(Initializer.class);
private ClientProperties properties;
public Initializer(ClientProperties properties) {
this.properties = properties;
}
public void run(String... args) throws Exception {
}
}
在 run 方法中构建必要的对象以便与客户端点交互。
创建一个 RestClient
要创建 RestClient 的实例,我们有可用的方便的静态方法:
- create() 方法在默认的 REST 客户端中进行委托。
- create(String url) 接受默认基本 url。
- create(RestTemplate restTemplate) 根据给定的 Rest 模板的配置初始化一个新的 RestClient。
- builder() 允许使用标头、错误处理程序、拦截器和更多选项自定义 RestClient。
- builder(RestTemplate restTemplate) 根据给定 RestTemplate 的配置获取 RestClient 构建器。
让我们使用构建器方法编写一个 RestClient 来调用客户 API。
RestClient restClient = RestClient.builder()
.baseUrl(properties.getUrl())
.defaultHeader(HttpHeaders.AUTHORIZATION,
encodeBasic(properties.getUsername(),
properties.getPassword())
).build();
让我们仔细看看上面的代码:
- baseUrl 方法是不言自明的。它设置客户端的基本 url
- defaultHeader 允许设置 http 标头。还有另一个名为 defaultHeaders 的方法,它将 Consumer 作为多个标头的参数。我们正在设置授权标头来传递凭据。
- properties 是一个简单的配置属性类,用于存储请求所需的其余 API 数据。它是通过构造函数注入到 CommmandLineRunner 类中的。
@Configuration
@ConfigurationProperties(prefix = "application.rest.v1.customer")
public class ClientProperties {
String url;
String username;
String password;
// getter/setter omitted
}
三个新的键值已添加到应用程序属性文件中
application.rest.v1.customer.url=http://localhost:8080/api/v1/customers
application.rest.v1.customer.username=user1234
application.rest.v1.customer.password=password5678
最后,encodeBasic例程仅供参考
private String encodeBasic(String username, String password) {
return "Basic "+Base64
.getEncoder()
.encodeToString((username+":"+password).getBytes());
}
接收数据
下一步是使用客户端发送 http 请求并接收响应。RestClient 为每个 HTTP 方法提供方法。例如,要搜索所有活跃客户,必须执行 GET 请求。检索方法获取响应并声明如何提取它。
让我们从一个简单的例子开始,将完整的正文获取为字符串。
String data = restClient.get()
.uri("?status={STATUS}&vip={vip}","activated", true)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(String.class);
logger.info(data);
uri方法可以设置http参数以按状态和vip进行过滤。第一个参数(字符串模板)是附加到 RestClient 中定义的基本 url 的查询字符串。第二个参数是模板的 uri 变量 (varargs)。
我们还将媒体类型指定为 JSON。输出显示在控制台中:
[{"id":6,"status":"ACTIVATED","personInfo":{"name":"name 6 surname 6","email":"organisation6@email.com","dateOfBirth":"19/07/1976"},"detailsInfo":{"info":"Customer info details 6","vip":true}}]
如果我们需要检查响应状态代码或响应标头怎么办?不用担心,toEntity 方法返回一个 ResponseEntity。
ResponseEntity response = restClient.get()
.uri("?status={STATUS}&vip={vip}","activated", true)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(String.class);
logger.info("Status " + response.getStatusCode());
logger.info("Headers " + response.getHeaders());
转换 JSON
RestClient还可以将响应正文转换为JSON格式。如果在类路径中检测到 Jackson 2 库或 Jackson 库,Spring 将默认自动注册 MappingJackson2HttpMessageConverter 或 MappingJacksonHttpMessageConverter。但您可以注册自己的消息转换器并覆盖默认设置。
在我们的例子中,响应可以直接转换为记录。例如,要从 API 检索特定客户:
CustomerResponse customer = restClient.get()
.uri("/{id}",3)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(CustomerResponse.class);
logger.info("Customer name: " + customer.personInfo().name());
并输出提取客户姓名
Customer name: name 3 surname 3
要搜索客户,我们只需要使用 List 类,如以下代码所示
List<CustomerResponse> customers = restClient.get()
.uri("?status={STATUS}&vip={vip}","activated", true)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(List.class);
logger.info("Customers size " + customers.size());
显示客户响应的记录类别以供参考
public record CustomerResponse(
long id,
String status,
CustomerPersonInfo personInfo,
CustomerDetailsInfo detailsInfo) {}
public record CustomerPersonInfo(
String name, String email, String dateOfBirth) {}
public record CustomerDetailsInfo(String info, boolean vip) {}
发布数据
要发送 post 请求,只需调用 post 方法。下一个代码片段创建一个新客户。
CustomerRequest customer = new CustomerRequest(
"John Smith",
"john.smith@mycompany.com",
LocalDate.of(1998, 10, 25),
"Customer detailed info here",
true
);
ResponseEntity<Void> response = restClient.post()
.accept(MediaType.APPLICATION_JSON)
.body(customer)
.retrieve()
.toBodilessEntity();
if (response.getStatusCode().is2xxSuccessful()) {
logger.info("Created " + response.getStatusCode());
logger.info("New URL " + response.getHeaders().getLocation());
}
响应代码确认客户已成功创建
Created 201 CREATED
New URL http://localhost:8080/api/v1/customers/11
为了验证客户是否已添加,可以通过邮递员检索上述 URL
{
"id": 11,
"status": "ACTIVATED",
"personInfo": {
"name": "John Smith",
"email": "john.smith@mycompany.com",
"dateOfBirth": "25/10/1998"
},
"detailsInfo": {
"info": "Customer detailed info here",
"vip": true
}
}
当然,可以使用 RestClient 来获取它,其代码类似于上一节。
显示客户请求的记录类别以供参考
public record CustomerRequest(
String name,
String email,
LocalDate dateOfBirth,
String info,
Boolean vip) { }
删除数据
发出 HTTP 删除请求来尝试删除资源就像调用 delete 方法一样简单。
ResponseEntity<Void> response = restClient.delete()
.uri("/{id}",2)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toBodilessEntity();
logger.info("Deleted with status " + response.getStatusCode());
值得一提的是,如果操作成功,响应正文将为空。对于这种情况,方法 toBodilessEntity 会派上用场。要删除的客户 ID 作为 uri 变量传递。
Deleted with status 204 NO_CONTENT
处理错误
如果我们尝试删除或检索不存在的客户会发生什么?客户端点将返回 404 错误代码以及消息详细信息。然而,只要收到客户端错误状态(400-499)或服务器错误状态(500-599),RestClient就会抛出RestClientException的子类。
要定义我们的自定义异常处理程序,有两个在不同级别上工作的选项:
- 在具有 defaultStatusHandler 方法的 RestClient 中(对于与其一起发送的所有 http 请求)
- 对于每个带有 onstatus 方法的 http 请求,调用 retreive 方法(该方法返回一个 ResponseSpec 接口)。
第一个是在此代码片段中呈现的
RestClient restClient = RestClient.builder()
.baseUrl(properties.getUrl())
.defaultHeader(HttpHeaders.AUTHORIZATION,
encodeBasic(properties.getUsername(),
properties.getPassword()))
.defaultStatusHandler(
HttpStatusCode::is4xxClientError,
(request, response) -> {
logger.error("Client Error Status " +
response.getStatusCode());
logger.error("Client Error Body "+new
String(response.getBody().readAllBytes()));
})
.build();
运行删除命令行运行程序后的控制台:
Client Error Status 404 NOT_FOUND
Client Error Body {"status":404,"message":"Entity Customer for id 2 was not found.","timestamp":"2023-07-23T09:24:55.4088208"}
另一个选项是实现删除操作的 onstatus 方法。它优先于 RestClient 默认处理程序行为。因此,如下面的代码行所示,它被覆盖
ResponseEntity response = restClient.delete()
.uri("/{id}",2)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError,
(req, res) ->
logger.error("Couldn't delete "+res.getStatusText())
)
.toBodilessEntity();
if (response.getStatusCode().is2xxSuccessful())
logger.info("Deleted with status " +
response.getStatusCode());
现在控制台中的消息将是
Couldn't delete Not Found
兑换方式
交换方法对于必须根据响应状态对响应进行不同解码的情况很有用。使用交换方法时,状态处理程序将被忽略。
在此虚构的示例代码中,响应根据状态映射到实体
SimpleResponse simpleResponse = restClient.get()
.uri("/{id}",4)
.accept(MediaType.APPLICATION_JSON)
.exchange((req,res) ->
switch (res.getStatusCode().value()) {
case 200 -> SimpleResponse.FOUND;
case 404 -> SimpleResponse.NOT_FOUND;
default -> SimpleResponse.ERROR;
}
);
概括
在本文中,我们介绍了 Spring 6.1 M1+ 附带的新 RestClient 的主要功能。现在,您应该能够在调用 Rest API 时执行最常见的任务,以及自定义错误处理并使用带有交换的低级别 http 请求/响应。
正如您所看到的,这个新 API 比旧的 RestTemplate 更容易管理。如果您的项目仅使用 WebClient API,它还可以避免添加反应式 Web 库。
- 点赞
- 收藏
- 关注作者
评论(0)