Spring 6.1 中的新 RestClient

举报
千锋教育 发表于 2023/07/28 15:26:09 2023/07/28
【摘要】 Spring中的HTTP客户端简介Spring框架提供了两种不同的选项来执行http请求:RestTemplate:它是十多年前在 Spring 3 中引入的。它是提供同步阻塞通信的模板模式的实现。WebClient:它在 Spring 5 中作为 Spring WebFlux 库的一部分发布。它提供流畅的 API 并遵循反应式模型。RestRemplate 方法暴露了太多的 HTTP 功能...

企业微信截图_20230728152540.jpg

Spring中的HTTP客户端简介

Spring框架提供了两种不同的选项来执行http请求:

  1. RestTemplate:它是十多年前在 Spring 3 中引入的。它是提供同步阻塞通信的模板模式的实现。
  2. 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 的实例,我们有可用的方便的静态方法:

  1. create() 方法在默认的 REST 客户端中进行委托。
  2. create(String url) 接受默认基本 url。
  3. create(RestTemplate restTemplate) 根据给定的 Rest 模板的配置初始化一个新的 RestClient。
  4. builder() 允许使用标头、错误处理程序、拦截器和更多选项自定义 RestClient。
  5. 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的子类。

要定义我们的自定义异常处理程序,有两个在不同级别上工作的选项:

  1. 在具有 defaultStatusHandler 方法的 RestClient 中(对于与其一起发送的所有 http 请求)
  2. 对于每个带有 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 库。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。