8.3.3 使用 WebClient 实现 REST 客户端
Spring 中 REST 客户端的现代和响应式选择是 WebClient。该框架提供了多种实例化 WebClient 对象的方式——在这个示例中,我们将使用 WebClient.Builder。请参阅官方文档以探索其他选项(https://spring.io/projects/spring-framework)。
在 com.polarbookshop.orderservice.config 包中,创建一个 ClientConfig 类来使用 ClientProperties 提供的基础 URL 配置 WebClient bean。
代码清单 8.20 配置 WebClient bean 以调用 Catalog Service
package com.polarbookshop.orderservice.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class ClientConfig {
@Bean
WebClient webClient(
ClientProperties clientProperties,
WebClient.Builder webClientBuilder // Spring Boot 自动配置的用于构建 WebClient bean 的对象
) {
return webClientBuilder
.baseUrl(clientProperties.catalogServiceUri().toString()) // 配置 WebClient 基础 URL 为 Catalog Service URL
.build();
}
}
警告: 如果你使用 IntelliJ IDEA,你可能会收到 WebClient.Builder 无法自动装配的警告。不用担心。这是误报。你可以通过使用 @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") 注解字段来消除警告。
接下来,在 com.polarbookshop.orderservice.book 包中创建一个 BookClient 类。在那里,你将使用 WebClient bean 通过其流畅的 API 向 Catalog Service 暴露的 GET /books/{bookIsbn} 端点发送 HTTP 调用。WebClient 最终将返回一个包装在 Mono 发布者中的 Book 对象。
代码清单 8.21 使用 WebClient 定义响应式 REST 客户端
package com.polarbookshop.orderservice.book;
import reactor.core.publisher.Mono;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
@Component
public class BookClient {
private static final String BOOKS_ROOT_API = "/books/";
private final WebClient webClient;
public BookClient(WebClient webClient) {
this.webClient = webClient; // 之前配置的 WebClient bean
}
public Mono<Book> getBookByIsbn(String isbn) {
return webClient
.get() // 请求应使用 GET 方法
.uri(BOOKS_ROOT_API + isbn) // 请求的目标 URI 是 /books/{isbn}
.retrieve() // 发送请求并检索响应
.bodyToMono(Book.class); // 将检索到的对象作为 Mono<Book> 返回
}
}
WebClient 是一个响应式 HTTP 客户端。你刚刚看到它如何将数据作为响应式发布者返回。特别是,调用 Catalog Service 获取有关特定书籍的详细信息的结果是一个 Mono
OrderService 类中的 submitOrder() 方法目前一直拒绝订单。但不会太久了。你现在可以自动装配一个 BookClient 实例,并使用底层的 WebClient 启动一个响应式流来处理书籍信息并创建订单。map() 运算符允许你将 Book 映射为接受的订单。如果 BookClient 返回空结果,你可以使用 defaultIfEmpty() 运算符定义被拒绝的订单。最后,通过调用 OrderRepository 保存订单(无论接受还是拒绝)来结束流。
代码清单 8.22 调用 BookClient 获取订购时的书籍信息
package com.polarbookshop.orderservice.order.domain;
import com.polarbookshop.orderservice.book.Book;
import com.polarbookshop.orderservice.book.BookClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final BookClient bookClient;
private final OrderRepository orderRepository;
public OrderService(BookClient bookClient, OrderRepository orderRepository) {
this.bookClient = bookClient;
this.orderRepository = orderRepository;
}
public Flux<Order> getAllOrders() {
return orderRepository.findAll();
}
public Mono<Order> submitOrder(String isbn, int quantity) {
return bookClient.getBookByIsbn(isbn) // 调用 Catalog Service 检查书籍可用性
.map(book -> buildAcceptedOrder(book, quantity)) // 如果书籍可用,接受订单
.defaultIfEmpty(buildRejectedOrder(isbn, quantity)) // 如果书籍不可用,拒绝订单
.flatMap(orderRepository::save); // 保存订单
}
public static Order buildAcceptedOrder(Book book, int quantity) {
return Order.of(book.isbn(), book.title(), book.author(), quantity, OrderStatus.ACCEPTED);
}
public static Order buildRejectedOrder(String bookIsbn, int quantity) {
return Order.of(bookIsbn, null, null, quantity, OrderStatus.REJECTED);
}
}
现在 Order Service 可以真正处理订单了。它调用 Catalog Service 来检查书籍是否可用,如果可用则接受订单,如果不可用则拒绝订单。
WebClient 还可以用于处理错误。例如,如果 Catalog Service 不可用,WebClient 将抛出一个异常。你可以使用 onError() 运算符来处理这种情况。让我们在下一节中探讨如何使应用程序更具弹性。