Перейти к основному содержимому

Что делает Mono.defer()?

· 4 мин. чтения

1. Обзор

В реактивном программировании есть много способов создать публикатор типа Mono или Flux . Здесь мы рассмотрим использование метода defer для задержки выполнения издателя Mono .

2. Что такое метод Mono.defer?

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

public static <T> Mono<T> defer(Supplier<? extends Mono<? extends T>> supplier)

Здесь defer принимает поставщика издателя Mono и лениво возвращает этот Mono при подписке ниже по течению.

Однако вопрос в том, что такое холодный издатель или ленивый издатель? Давайте посмотрим на это.

Исполняющий поток оценивает холодных издателей только тогда, когда потребители подписываются на них. В то время как горячий издатель оценивал с нетерпением перед любой подпиской. У нас есть метод Mono.just() , который дает горячего издателя типа Mono .

3. Как это работает?

Давайте рассмотрим пример использования с Поставщиком типа Mono :

private Mono<String> sampleMsg(String str) {
log.debug("Call to Retrieve Sample Message!! --> {} at: {}", str, System.currentTimeMillis());
return Mono.just(str);
}

Здесь этот метод возвращает горячего издателя Mono . Давайте охотно подпишемся на это:

public void whenUsingMonoJust_thenEagerEvaluation() throws InterruptedException {

Mono<String> msg = sampleMsg("Eager Publisher");

log.debug("Intermediate Test Message....");

StepVerifier.create(msg)
.expectNext("Eager Publisher")
.verifyComplete();

Thread.sleep(5000);

StepVerifier.create(msg)
.expectNext("Eager Publisher")
.verifyComplete();
}

При выполнении в логах видим следующее:

20:44:30.250 [main] DEBUG com.foreach.mono.MonoUnitTest - Call to Retrieve Sample Message!! --> Eager Publisher at: 1622819670247
20:44:30.365 [main] DEBUG reactor.util.Loggers$LoggerFactory - Using Slf4j logging framework
20:44:30.365 [main] DEBUG com.foreach.mono.MonoUnitTest - Intermediate Test Message....

Здесь мы можем заметить, что:

  • Согласно последовательности инструкций, основной поток с готовностью выполняет метод sampleMsg .
  • В обеих подписках, использующих StepVerifier , основной поток использует один и тот же вывод sampleMsg . Поэтому никаких новых оценок.

Давайте посмотрим, как Mono.defer() преобразует его в холодного (ленивого) публикатора:

public void whenUsingMonoDefer_thenLazyEvaluation() throws InterruptedException {

Mono<String> deferMsg = Mono.defer(() -> sampleMsg("Lazy Publisher"));

log.debug("Intermediate Test Message....");

StepVerifier.create(deferMsg)
.expectNext("Lazy Publisher")
.verifyComplete();

Thread.sleep(5000);

StepVerifier.create(deferMsg)
.expectNext("Lazy Publisher")
.verifyComplete();

}

При выполнении этого метода мы видим в консоли следующие логи:

20:01:05.149 [main] DEBUG com.foreach.mono.MonoUnitTest - Intermediate Test Message....
20:01:05.187 [main] DEBUG com.foreach.mono.MonoUnitTest - Call to Retrieve Sample Message!! --> Lazy Publisher at: 1622817065187
20:01:10.197 [main] DEBUG com.foreach.mono.MonoUnitTest - Call to Retrieve Sample Message!! --> Lazy Publisher at: 1622817070197

Здесь мы можем заметить несколько моментов из последовательности журнала:

  • StepVerifier выполняет метод sampleMsg для каждой подписки, а не тогда, когда мы его определили.
  • После задержки в 5 секунд второй потребитель, подписавшийся на метод sampleMsg , снова выполняет его.

Вот как метод отсрочки превращает горячего в холодного издателя.

4. Примеры использования Mono.defer ?

Давайте рассмотрим возможные варианты использования метода Mono.defer() :

  • Когда мы должны условно подписаться на издателя
  • Когда каждое выполнение по подписке может привести к другому результату
  • deferContextual можно использовать для текущей контекстной оценки издателя.

4.1. Пример использования

Давайте рассмотрим один пример, в котором используется условный метод Mono.defer() :

public void whenEmptyList_thenMonoDeferExecuted() {

Mono<List<String>> emptyList = Mono.defer(() -> monoOfEmptyList());

//Empty list, hence Mono publisher in switchIfEmpty executed after condition evaluation
Flux<String> emptyListElements = emptyList.flatMapIterable(l -> l)
.switchIfEmpty(Mono.defer(() -> sampleMsg("EmptyList")))
.log();

StepVerifier.create(emptyListElements)
.expectNext("EmptyList")
.verifyComplete();
}

Здесь поставщик Mono издателя sampleMsg помещается в метод switchIfEmpty для условного выполнения. Следовательно, sampleMsg выполняется только тогда, когда на него лениво подписаны.

Теперь давайте посмотрим на тот же код для непустого списка:

public void whenNonEmptyList_thenMonoDeferNotExecuted() {

Mono<List<String>> nonEmptyist = Mono.defer(() -> monoOfList());

//Non empty list, hence Mono publisher in switchIfEmpty won't evaluated.
Flux<String> listElements = nonEmptyist.flatMapIterable(l -> l)
.switchIfEmpty(Mono.defer(() -> sampleMsg("NonEmptyList")))
.log();

StepVerifier.create(listElements)
.expectNext("one", "two", "three", "four")
.verifyComplete();
}

Здесь sampleMsg не выполняется, потому что на него нет подписки.

5. Вывод

В этой статье мы обсудили метод Mono.defer() и горячие/холодные издатели. Кроме того, как мы можем превратить горячего издателя в холодного издателя. Наконец, мы также обсудили его работу с примерами использования.

Как всегда, пример кода доступен на GitHub .