1. 概要

リアクティブプログラミングでは、タイプMonoまたはFluxのパブリッシャーを作成する方法がたくさんあります。 ここでは、 defer メソッドを使用して、Monoパブリッシャーの実行を遅らせる方法について説明します。

2. Mono.deferメソッドとは何ですか?

Monodeferメソッドを使用して、最大で1つの値を生成できるコールドパブリッシャーを作成できます。 メソッドのシグネチャを見てみましょう。

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

ここで、 defer は、 MonoパブリッシャーのSupplierを取り込み、ダウンストリームでサブスクライブすると、その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....

ここで、次のことに気付くことができます。

  • 命令シーケンスに従って、mainスレッドはメソッドsampleMsgを熱心に実行します。
  • StepVerifier を使用する両方のサブスクリプションで、mainスレッドは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にサブスクライブしている2番目のコンシューマーがそれを再度実行します。

これが、deferメソッドがホットからコールドパブリッシャーに変わる方法です。

4. Mono.defer のユースケース?

Mono.defer()メソッドを使用できる可能性のあるユースケースを見てみましょう。

  • 条件付きで発行者にサブスクライブする必要がある場合
  • サブスクライブされた実行ごとに異なる結果が生成される可能性がある場合
  • deferContextual は、パブリッシャーの現在のコンテキストベースの評価に使用できます

4.1. 使用例

条件付きMono.defer()メソッドを使用している1つのサンプルを見てみましょう。

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パブリッシャーsampleMsgsupplierは、条件付き実行のために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から入手できます。