反应性 Spring 引导:return 在地图完成之前评估

Reactive Spring Boot: return evaluates before map finshed

我正在使用反应式 WebClient 构建一个 API 与其他 2 个 API 进行通信。 API2 需要从 API1 获取信息,然后我的服务结合 returns 这两个信息。 资源:

@GetMapping("monoMedication/{medID}")
    public  Mono<Object> getMonoMedication(@PathVariable String medID) throws SSLException {
           Mono<Login> Login =createWebClient()
                .post()
                .uri("URI_LOGIN_API1" )
                .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromObject(body))
                .retrieve()
                .bodyToMono(Login.class);

        return Login.map(login-> {
                       Mono<String> medicationBundles = null;
            try {
                 medicationBundles = createWebClient()
                        .post()
                        .uri("URI_API1_GET_DATA")
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromObject("Information"))
                        .header("Authorization", login.getSessionId())
                        .retrieve()
                       .bodyToMono(String.class);

            } catch (SSLException e) {
                e.printStackTrace();
            }

            return  medicationBundles.map(bundles_string -> {
                try {
                    List<Object> bundle_list = mapper.readValue(bundles_string, new TypeReference<List<Object>>(){});
                    bundle_list.forEach(bundle-> processBundle(bundle,medicationList));

                    return medicationList;
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
                    return null;
                })
        })
    } 

函数:

List<String> medicationList = new ArrayList<>();
    private void processBundle(Object bundle, List<String> medicationlist) {
       //do something to get id from bundle
        String ID = bundle.getID();
// if i add something to medicationList.add(ID) here, it is in the required return of my API
        Mono<String> Medication = 
            webClientBuilder.build()
            .get()
            .uri("URI_API2_GET_DATA"+ID)
            .retrieve()
            .bodyToMono(String.class);

         Medication.map(medication_ID -> {
        //do something to get information from medication_ID
            String info = medication_ID.getInfo();

            //this Information comes after the required return
            return medicationList.add(info+ID);
        }).subscribe();
    }

我的问题是,return 在所需的最后一张地图完成之前出现。我不知何故错过了一些东西。我尝试了不同的方法,例如then(), thenMany(), thenReturn() 在不同的位置。 有没有办法做到这一点?如果有一个可能已经完成的简单示例,那也会有所帮助!

很难在您的代码中跟进,因为您正在以非最佳实践方式混合和匹配反应式编程与命令式编程。

您的代码无法编译,您有一些奇怪的事情,例如 medID 从未被使用,变量从未被声明,例如 body。所以我只是“按原样”使用了您的代码,我没有制作一个完整的示例,只是一个指南。

您应该选择走被动路线还是命令路线。我的部分回答会自以为是,如果以后有人抱怨,那就是免责声明。

首先,您在每个请求中都创建了多个 WebClients 在我看来,这是一种不好的做法。创建 WebClient 是一种昂贵的不必要的操作,因为您可以重复使用它们,因此您应该在启动期间声明您的 Web 客户端并 @Autowire 它们。

@Configuration
public class WebClientConfig {


    @Bean
    @Qualifier("WebClient1")
    public WebClient createWebClient1() {
        return WebClient.create( ... );
    }

    @Bean
    @Qualifier("WebClient2")
    public WebClient createWebClient2() {
        return WebClient.create( ... );
    }

    @Bean
    @Qualifier("WebClient3")
    public WebClient createWebClient3() {
        return WebClient.create( ... );
    }
}

然后通过将它们自动连接到您的 class.

来使用它们

清理代码并将其分解为函数后,使用适当的 returns 我希望这能让您了解我将如何构建它。你的问题是你没有从你的函数中正确地 returning,并且你没有链接 returns。一旦你需要使用 subscribe 你通常就知道你做错了什么。

@RestController
public class FooBar {

    private WebClient webClient1;
    private WebClient webClient2;
    private WebClient webClient3;

    @Autowire
    public Foobar(@Qualifier("WebClient1") WebClient webclient1, @Qualifier("WebClient2") WebClient webclient2, @Qualifier("WebClient3") WebClient webclient3) {
        this.webClient1 = webClient1;
        this.webClient2 = webClient2;
        this.webClient3 = webClient3;
    }

    @GetMapping("monoMedication/{medID}")
    public Mono<List<MedicationData>> getMonoMedication(@PathVariable String medID) {
        return doLogin()
            .flatMap(login -> {
                return getMedicationBundles(login.getSessionId());
            }).flatMap(medicationBundles -> {
                return getMedicationData(medicationBundle.getId());
            }).collectList();
    }

    private Mono<Login> doLogin() {
         return webClient1
                .post()
                .uri("URI_LOGIN_API1")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .bodyValue(body)
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, response -> ...)
                .onStatus(HttpStatus::is5xxServerError, response -> ...)
                .bodyToMono(Login.class);
    }

    private Flux<MedicationBundle> getMedicationBundles(String sessionId) {
        return webClient2
                .post()
                .uri("URI_API1_GET_DATA")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .bodyValue("Information")
                .header("Authorization", sessionId)
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, response -> ...)
                .onStatus(HttpStatus::is5xxServerError, response -> ...)
                .bodyToFlux(MedicationBundle.class);
    }

    private Mono<String> getMedicationData(String medicationId) {
        return webClient3.get()
            .uri(uriBuilder - > uriBuilder
                    .path("/URI_API2_GET_DATA/{medicationId}")
                    .build(medicationId))
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError, response -> ...)
            .onStatus(HttpStatus::is5xxServerError, response -> ...)
            .bodyToMono(MedicationData.class);
    }
}

我是徒手写的,没有任何内容 IDE 它更多的是向您展示您应该如何构建代码,希望这能给您一些指导。

响应式编程中的一些注意事项:

  • 避免使用 try/catch 块,在响应式编程中,您通常 return a Mono.empty() 忽略错误,或者 return a Mono.error() 包含异常。

  • 使用 flatMap 异步处理,map 同步处理,还有其他几个运算符,如 concatMapflatMapSequential 保持顺序,解释这些本身就是一个单独的答案。

  • 尽可能避免使用void方法,总是尝试使用pure functions(避免在void函数中操作列表,你用C++中的指针而不是Java),您可以通过链接 .then() 运算符从反应函数 return Mono<Void>

  • 尽可能利用类型系统,尽可能不要序列化到 Object 创建数据的对象表示并序列化到它。

  • 除非您是数据的消费者,否则几乎从不订阅您的应用程序。发起调用的通常是 subscriber,您的应用程序通常是 producer,而调用客户端(Web 或其他服务)是 subscriber。多个 subscribers 通常是一种代码味道。

  • 始终尝试 return 并链接 return。我在 return 上面写的所有函数和 return 上的链,这就是所谓的 construction belt analogy。我个人总是从在第一行写 return 语句开始每个函数,然后我开始写我的反应性东西。永远不要在没有 return 链接的情况下留下 MonoFlux,因为 MonoFlux 永远不会得到 运行 如果没有一个订阅它。

  • 将代码结构化为函数,编写函数是免费的:)

一些好的读物和视频:

Getting started Reactor

Webflux official documentation (我建议先阅读官方的 reactor 文档,这些文档可能很难理解,除非你非常了解 reactor)

来自 spring 一个的好视频,这个几乎解决了我上面写的所有内容。 Do's and don't in reactive programming

如前所述,这有点基于个人意见,但希望这能为您提供一些指导。