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 .