0

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

@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();
    }

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

4

1 回答 1

2

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

您的代码无法编译,并且您遇到了一些奇怪的事情,例如medID从未使用过和从未像body. 所以我只是“按原样”采用你的代码,我没有制作一个完整的工作示例,只是一个指南。

您应该选择采用反应式路线或命令式路线。我的回答的某些部分会是固执己见,如果以后有人会抱怨,那就是免责声明。

首先,您在每个请求中都创建了几个WebClients,我认为这被认为是不好的做法。创建 aWebClient是一种昂贵且不需要的操作,因为您可以重用它们,因此您应该在启动时声明您的 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( ... );
    }
}

然后通过将它们自动连接到您的班级来使用它们。

在清理您的代码并将其划分为函数后,我希望这能让您了解我将如何构建它。您的问题是您没有从函数中正确返回,并且您没有链接返回。一旦你需要使用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 块,在反应式编程中,您通常要么返回 aMono.empty()以忽略错误,要么返回Mono.error()包含异常的 a。

  • 用于flatMap异步处理事物,map用于同步处理,还有其他几个像concatMap和一样的运算符,flatMapSequential它们保持顺序,解释这些本身就是一个单独的答案。

  • void如果可以,请避免使用方法,始终尝试使用纯函数(避免在 void 函数中操作列表,您可以使用 C++ 中的指针而不是 Java 中的指针),您可以Mono<Void>通过链接.then()操作符从反应式函数返回。

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

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

  • 始终尝试返回并链接返回。我上面写的所有函数都返回一些东西和一个返回链,这就是所谓的construction belt analogy. 我个人总是从在第一行写 return 语句开始每个函数,然后我开始写我的反应性东西。永远不要离开 a MonoorFlux而不返回并链接到它,因为如果没有人订阅它, a Monoor将永远不会运行。Flux

  • 将您的代码构造成函数,编写函数是免费的 :)

一些不错的读物和视频:

反应堆入门

Webflux 官方文档 (我建议先阅读官方 reactor 文档,除非您对 reactor 非常了解,否则这些文档可能很难理解)

春季第一的好视频,这个几乎解决了我上面写的所有内容。 反应式编程中的注意事项

如前所述,这有点基于意见,但希望这会给你一些指导。

于 2020-07-18T10:59:04.103 回答