Spring WebClient vs. RestTemplate

1. 前書き

このチュートリアルでは、Springの2つのWebクライアント実装を比較します。 spring-5-webclient [WebClient] _。

2. ブロッキングと ノンブロッキングクライアント

Webアプリケーションでは、他のサービスへのHTTP呼び出しを行うことが一般的な要件です。 したがって、Webクライアントツールが必要です。

2.1. _RestTemplate_ブロッキングクライアント

長い間、Springは_RestTemplate_をWebクライアントの抽象化として提供してきました。 内部では、* _ RestTemplate_は要求ごとのスレッドモデル*に基づいたJavaサーブレットAPIを使用します。
これは、Webクライアントが応答を受信するまでスレッドがブロックされることを意味します。 ブロッキングコードの問題は、各スレッドがある程度のメモリとCPUサイクルを消費するためです。
結果を生成するために必要な低速のサービスを待機している多数の着信要求があることを考えてみましょう。
遅かれ早かれ、結果を待っているリクエストは山積みになります。 *その結果、アプリケーションは多くのスレッドを作成し、スレッドプールを使い果たしたり、利用可能なメモリをすべて占有します*。 CPUコンテキスト(スレッド)の頻繁な切り替えにより、パフォーマンスが低下することもあります。

2.2. _WebClient_ノンブロッキングクライアント

一方、* _ WebClient_は、Spring Reactiveフレームワーク*が提供する非同期の非ブロッキングソリューションを使用します。
_RestTemplate_は各イベント(HTTP呼び出し)に対して新しい_Thread_を作成しますが、_WebClient_は各イベントに対して「タスク」のようなものを作成します。 背後で、Reactiveフレームワークはそれらの「タスク」をキューに入れ、適切な応答が利用可能になったときにのみそれらを実行します。
Reactiveフレームワークは、イベント駆動型アーキテクチャを使用します。 link:/java-9-reactive-streams[Reactive Streams API]を介して非同期ロジックを構成する手段を提供します。 その結果、リアクティブアプローチは、同期/ブロック方式と比較して、使用するスレッドとシステムリソースを減らしながら、より多くのロジックを処理できます。
_WebClient_は、https://www.baeldung.com/spring-webflux [Spring WebFlux]ライブラリの一部です。 したがって、*さらに、リアクティブ型(_Mono_および_Flux_)を備えた機能的で流fluentなAPIを宣言的な構成として使用して、クライアントコードを記述することができます*。

3. 比較例

これら2つのアプローチの違いを実証するには、多数の同時クライアント要求でパフォーマンステストを実行する必要があります。 特定の数の並列クライアント要求の後、ブロッキングメソッドを使用するとパフォーマンスが大幅に低下することがわかります。
一方、リアクティブ/ノンブロッキングメソッドは、リクエストの数に関係なく、一定のパフォーマンスを提供する必要があります。
この記事の目的のために、2つのRESTエンドポイントを実装しましょう。1つは_RestTemplate_を使用し、もう1つは_WebClient_ *を使用します。 彼らのタスクは、ツイートのリストを返す別の遅いREST Webサービスを呼び出すことです。
はじめに、https://search.maven.org/search?q = a:spring-boot-starter-webflux [Spring Boot WebFluxスターター依存関係]が必要です。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
さらに、遅いサービスRESTエンドポイントを次に示します。
@GetMapping("/slow-service-tweets")
private List<Tweet> getAllTweets() {
    Thread.sleep(2000L); // delay
    return Arrays.asList(
      new Tweet("RestTemplate rules", "@user1"),
      new Tweet("WebClient is better", "@user2"),
      new Tweet("OK, both are useful", "@user1"));
}

3.1. _RestTemplate_を使用して低速サービスを呼び出す

次に、Webクライアント経由で低速サービスを呼び出す別のRESTエンドポイントを実装しましょう。
まず、_RestTemplate_を使用します。
@GetMapping("/tweets-blocking")
public List<Tweet> getTweetsBlocking() {
    log.info("Starting BLOCKING Controller!");
    final String uri = getSlowServiceUri();

    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<List<Tweet>> response = restTemplate.exchange(
      uri, HttpMethod.GET, null,
      new ParameterizedTypeReference<List<Tweet>>(){});

    List<Tweet> result = response.getBody();
    result.forEach(tweet -> log.info(tweet.toString()));
    log.info("Exiting BLOCKING Controller!");
    return result;
}
_RestTemplate_の同期的な性質により、このエンドポイントを呼び出すと、コードは遅いサービスからの応答を待つことをブロックします。 応答が受信された場合にのみ、このメソッドの残りのコードが実行されます。 ログには以下が表示されます。
Starting BLOCKING Controller!
Tweet(text=RestTemplate rules, [email protected])
Tweet(text=WebClient is better, [email protected])
Tweet(text=OK, both are useful, [email protected])
Exiting BLOCKING Controller!

3.2. _WebClient_を使用して低速サービスを呼び出す

次に、_WebClient_を使用して遅いサービスを呼び出しましょう。
@GetMapping(value = "/tweets-non-blocking",
            produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Tweet> getTweetsNonBlocking() {
    log.info("Starting NON-BLOCKING Controller!");
    Flux<Tweet> tweetFlux = WebClient.create()
      .get()
      .uri(getSlowServiceUri())
      .retrieve()
      .bodyToFlux(Tweet.class);

    tweetFlux.subscribe(tweet -> log.info(tweet.toString()));
    log.info("Exiting NON-BLOCKING Controller!");
    return tweetFlux;
}
この場合、_WebClient_は_Flux_パブリッシャーを返し、メソッドの実行が完了します。 結果が利用可能になると、パブリッシャーはサブスクライバーにツイートを送信し始めます。 この_ / tweets-non-blocking_エンドポイントを呼び出すクライアント(この場合はWebブラウザー)も、返された_Flux_オブジェクトにサブスクライブされることに注意してください。
今回はログを観察しましょう。
Starting NON-BLOCKING Controller!
Exiting NON-BLOCKING Controller!
Tweet(text=RestTemplate rules, [email protected])
Tweet(text=WebClient is better, [email protected])
Tweet(text=OK, both are useful, [email protected])
このエンドポイントメソッドは、応答を受信する前に完了したことに注意してください。

4. 結論

この記事では、SpringでWebクライアントを使用する2つの異なる方法を検討しました。
_RestTemplate_はJava Servlet APIを使用するため、同期的でブロックされます。 反対に、_WebClient_は非同期であり、応答が戻るのを待っている間、実行中のスレッドをブロックしません。 *応答の準備ができたときにのみ通知が生成されます。*
_RestTemplate_は引き続き使用されます。 場合によっては、非ブロック化アプローチは、ブロック化システムに比べて使用するシステムリソースがはるかに少なくなります。 したがって、これらの場合、_WebClient_が望ましい選択です。
記事に記載されているすべてのコードスニペットは、https://github.com/eugenp/tutorials/tree/master/spring-5-reactive-2 [GitHubで]にあります。