«1. Обзор
В реактивном программировании существует множество способов создания издателя типа Mono или Flux. Здесь мы рассмотрим использование метода defer для задержки выполнения издателя Mono.
2. Что такое метод Mono.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.baeldung.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.baeldung.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.baeldung.mono.MonoUnitTest - Intermediate Test Message....
20:01:05.187 [main] DEBUG com.baeldung.mono.MonoUnitTest - Call to Retrieve Sample Message!! --> Lazy Publisher at: 1622817065187
20:01:10.197 [main] DEBUG com.baeldung.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.