Spring Boot WebClient.Builder bean 在传统 servlet 多线程应用程序中的使用
Posted
技术标签:
【中文标题】Spring Boot WebClient.Builder bean 在传统 servlet 多线程应用程序中的使用【英文标题】:Spring Boot WebClient.Builder bean usage in traditional servlet multi threaded application 【发布时间】:2019-06-05 18:14:01 【问题描述】:我希望有一个 http 客户端从 Spring Boot not reactive 应用程序中调用其他微服务。由于 RestTemplate 将被弃用,我尝试使用 WebClient.Builder 和 WebClient。虽然我不确定线程安全。这里的例子:
@Service
public class MyService
@Autowired
WebClient.Builder webClientBuilder;
public VenueDTO serviceMethod()
//!!! This is not thread safe !!!
WebClient webClient = webClientBuilder.baseUrl("http://localhost:8000").build();
VenueDTO venueDTO = webClient.get().uri("/api/venue/id", bodDTO.getBusinessOutletId()).
retrieve().bodyToMono(VenueDTO.class).
blockOptional(Duration.ofMillis(1000)).
orElseThrow(() -> new BadRequestException(venueNotFound));
return VenueDTO;
本示例中的serviceMethod() 将从几个线程中调用,webClientBuilder 是单个bean 实例。 WebClient.Builder 类包含状态:baseUrl,这似乎不是线程安全的,因为很少有线程可以同时调用此状态更新。同时,WebClient 本身似乎是线程安全的,正如Right way to use Spring WebClient in multi-thread environment 的回答中提到的那样
我应该使用https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-webclient.html 中提到的 WebClient.Builder bean
Spring Boot 为您创建和预配置一个 WebClient.Builder;它 强烈建议将其注入您的组件中并用于 创建 WebClient 实例。
我看到的解决方法之一是创建 WebClient 而不将任何状态传递给构建器,而不是:
WebClient webClient = webClientBuilder.baseUrl("http://localhost:8000").build();
我会的:
WebClient webClient = webClientBuilder.build();
并在 uri 方法调用中传递带有协议和端口的完整 url:
webClient.get().uri("full url here", MyDTO.class)
在我的情况下使用它的正确方法是什么?
【问题讨论】:
【参考方案1】:你说得对,WebClient.Builder
不是线程安全的。
Spring Boot 正在创建 WebClient.Builder
作为原型 bean,因此您将为每个注入点获得一个新实例。就您而言,我认为您的组件似乎有点奇怪。
它应该看起来像这样:
@Service
public class MyService
private final WebClient webClient;
public MyService(WebClient.Builder webClientBuilder)
this.webClient = webClientBuilder.baseUrl("http://localhost:8000").build();
public VenueDTO serviceMethod()
VenueDTO venueDTO = webClient.get().uri("/api/venue/id", bodDTO.getBusinessOutletId()).
retrieve().bodyToMono(VenueDTO.class).
blockOptional(Duration.ofMillis(1000)).
orElseThrow(() -> new BadRequestException(venueNotFound));
return VenueDTO;
现在我猜这是一个代码 sn-p 并且您的应用程序可能有不同的约束。
如果您的应用程序需要经常更改基本 URL,那么我认为您应该停止在构建器上配置它并传递问题中提到的完整 URL。如果您的应用程序有其他需求(用于身份验证的自定义标头等),那么您也可以在构建器或基于每个请求的基础上执行此操作。
一般来说,您应该尝试为每个组件构建一个 WebClient
实例,因为为每个请求重新创建它非常浪费。
如果您的应用程序有非常具体的约束并且确实需要创建不同的实例,那么您可以随时调用 webClientBuilder.clone()
并获取一个可以变异的构建器新实例,而不会出现线程安全问题。
【讨论】:
我的@Service 需要通过 http 与几个微服务通信。我知道它们的功能,但收件人可能会在运行时更改。所以我认为我可以在构造函数中注入 WebClient.Builder 并构建我的 WebClient 实例,而无需在 WebClient.Builder 中调用 baseUrl。在这种情况下,所有远程微服务似乎只需要一个 WebClient 实例。对吗? 是的; baseUrl 只是一种方便,以防所有请求都发送到同一主机。这不是您的情况,因此您应该从构建器阶段删除它,并使用完整的 URL 发出您的请求 刚刚检查 WebClient.Builder 是原型 bean。我会选择你建议的路线。我无法在docs.spring.io/spring-boot/docs/current/reference/html/… 找到我的问题的所有答案,我尝试在我的服务方法中使用构建器,因为最初认为 WebClient 不是线程安全的(虽然它看起来确实是)。谢谢! 您从哪里获得 Spring Boot 正在创建 WebClient.Builder 作为原型 bean 的信息?我刚刚调试了一个带有 2 个客户端的应用程序,并且注入了同一个构建器实例!我现在正在寻找解决这个问题的方法... 这里是自动配置的链接:github.com/spring-projects/spring-boot/blob/… 如果你有一个显示这个问题的示例应用程序,你可以在 Spring Boot 项目上提交一个问题。以上是关于Spring Boot WebClient.Builder bean 在传统 servlet 多线程应用程序中的使用的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Spring Boot 应用程序 pom 同时需要 spring-boot-starter-parent 和 spring-boot-starter-web?
《02.Spring Boot连载:Spring Boot实战.Spring Boot核心原理剖析》