Моно switchIfEmpty излучает дважды

У меня есть сценарий, в котором мне нужно найти метод, возвращающий строковый ответ из внешнего API. У меня есть две возможности для этого ответа: дать мне действительный ответ (с параметром 1 или параметром 2) или, если оба ответа недействительны, вернуть в цепочку окончательного пустого издателя.

Mono<String> checkResponse(String parameter)
  1. Проверить, приемлем ли вызов checkResponse(parameter1), игнорировать второй вызов (switchIfEmpty) и продолжить цепочку или
  2. Проверьте, приемлем ли вызов checkResponse(parameter2) и продолжите цепочку, или
  3. вернуть Mono.Empty() и отбросить цепочку

на самом деле у меня есть

checkResponse(stringArg1)
        .switchIfEmpty(checkResponse(stringArg2))
        .flatMapMany ...
        .flatMap ...

метод

public Mono<String> checkResponse(String s)
return webClient.post()
                    .uri(URI)
                    .body(BodyInserters.fromValue(s))
                    .retrieve()
                    .bodyToMono(String.class)

Но switchIfEmpty всегда выполняется.

С уважением,

что вы подразумеваете под "всегда выполнять"? И в том, и в другом случае, когда тело есть, и в случае, когда тела нет? Вы проверили это? А если есть, то как на самом деле?

kerbermeister 04.02.2023 01:05

Я делаю mapNotNull и проверяю, является ли ответ строкового узла пустым или нет. Если не пустой, верните строку, иначе верните ноль.

Lucho82 04.02.2023 01:13

Я имею в виду, что когда результат не равен нулю, метод вызывается/подписывается дважды и не игнорирует (пропускает) метод switchIfEmpty.

Lucho82 04.02.2023 01:21
Выполнение HTTP-запроса с помощью Spring WebClient: GET
Выполнение HTTP-запроса с помощью Spring WebClient: GET
WebClient - это реактивный веб-клиент, представленный в Spring 5. Это реактивное, неблокирующее решение, работающее по протоколу HTTP/1.1.
0
3
107
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы уверены, что он на самом деле излучает дважды?

Важно понимать два аспекта Project Reactor:

  1. При сборке
  2. По подписке

Этот код:

checkResponse(stringArg1)
        .switchIfEmpty(checkResponse(stringArg2));

соберет Monos для обоих вызовов checkResponse.

По сути, checkResponse-метод вызывается дважды, однако только Mono, возвращенный из первого checkResponse-вызова, будет подписан до тех пор, пока он выдает элемент.

Вы можете проверить это поведение следующим образом:

checkResponse(stringArg1)
        .doOnSubscribe(s -> System.out.println("First checkResponse subscription"))
        .switchIfEmpty(checkResponse(stringArg2)
            .doOnSubscribe(s -> System.out.println("Second checkResponse subscription"))
        );

Что очень типично для реактивного кода, так это то, что код верхнего уровня в методе, который возвращает Mono/Flux, обычно выполняется во время сборки, в то время как все лямбда-выражения, переданные их различным операторам, таким как map/flatMap/concatMap/и т. д., выполняются во время подписки.
Проиллюстрировать:

public Mono<String> getName(int id) {
    // Assembly time
    System.out.println("This executes at assembly time");

    return userRepo.get(id)
        .map(user -> {
            // Subscription time
            System.out.println("This executes at subscription time");

            return user.name;
        });
}

Если сборка Mono может быть дорогой, а на нее никогда не будет подписки, как в вашем случае здесь, вы можете отложить сборку до времени подписки, используя Mono.defer:

checkResponse(stringArg1)
        .switchIfEmpty(Mono.defer(() -> checkResponse(stringArg2)));

На самом деле есть разница между временем сборки и временем подписки.

Время сборки — это когда вы создаете свой пайплайн, выстраивая реактивную цепочку.

Время подписки — это когда запускается выполнение и начинают поступать данные. Вы должны рассмотреть возможность использования обратных вызовов и лямбда-выражений, поскольку они лениво оцениваются.

Итак, ваш метод checkResponse() вызывается дважды во время сборки, потому что это не лямбда, а обычный метод. И он возвращает моно

Вы можете использовать Mono.defer(() -> checkResponse()) и отложить выполнение и сборку внутреннего моно, пока не оформите подписку.

Другие вопросы по теме