一尘不染

Spring Boot WebClient.Builder bean在传统servlet多线程应用程序中的用法

spring-boot

我想有一个http客户端从Spring Boot 不响应的
应用程序调用其他微服务。由于将不使用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本身似乎是线程安全的,如在多线程环境中使用SpringWebClient的正确方法中的回答中所述

我是否应该使用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)

在我的情况下使用它的正确方法是什么?


阅读 307

收藏
2020-05-30

共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;
    }
}

现在,我猜这是一个代码片段,您的应用程序可能具有不同的约束。

如果您的应用程序需要经常更改基本URL,那么我认为您应该停止在构建器上对其进行配置,并按照问题中的说明传递完整的URL。如果您的应用程序还有其他需求(用于身份验证的自定义标头等),那么您也可以在构建器上或根据每个请求执行此操作。

通常,您应该尝试WebClient为每个组件构建一个实例,因为为每个请求重新创建它是非常浪费的。

如果您的应用程序具有非常特定的约束并且确实需要创建不同的实例,那么您始终可以调用webClientBuilder.clone()并获取可以突变的构建器的新实例,而不会出现线程安全问题。

2020-05-30