1. 概要

このチュートリアルでは、Springの2つのWebクライアント実装( RestTemplateと新しいSpring5のリアクティブ代替WebClient)を比較します。

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

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

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

Springは長い間、Webクライアントの抽象化としてRestTemplateを提供してきました。 内部的には、 RestTemplateは、リクエストごとのスレッドモデルに基づくJavaサーブレットAPIを使用します。

これは、Webクライアントが応答を受信するまでスレッドがブロックされることを意味します。 ブロッキングコードの問題は、各スレッドがある程度のメモリとCPUサイクルを消費することによるものです。

結果を生成するために必要な遅いサービスを待っている多くの着信要求があることを考えてみましょう。

遅かれ早かれ、結果を待っているリクエストは山積みになります。 その結果、アプリケーションは多くのスレッドを作成し、スレッドプールを使い果たしたり、使用可能なすべてのメモリを占有したりします。 CPUコンテキスト(スレッド)の切り替えが頻繁に発生するため、パフォーマンスが低下する可能性もあります。

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

一方、 WebClientは、SpringReactiveフレームワークによって提供される非同期の非ブロッキングソリューションを使用します。

RestTemplate は各イベント(HTTP呼び出し)に呼び出し元スレッドを使用しますが、WebClientは各イベントに「タスク」のようなものを作成します。 舞台裏では、Reactiveフレームワークはそれらの「タスク」をキューに入れ、適切な応答が利用可能な場合にのみそれらを実行します。

Reactiveフレームワークは、イベント駆動型アーキテクチャを使用します。 Reactive StreamsAPIを介して非同期ロジックを構成する手段を提供します。 その結果、リアクティブアプローチは、同期/ブロッキング方式と比較して、より少ないスレッドとシステムリソースを使用しながら、より多くのロジックを処理できます。

WebClient は、 SpringWebFluxライブラリの一部です。 したがって、宣言型構成としてリアクティブタイプ(MonoおよびFlux)を備えた機能的で流暢なAPIを使用してクライアントコードを記述することもできます。

3. 比較例

これら2つのアプローチの違いを示すために、多数の同時クライアント要求を使用してパフォーマンステストを実行する必要があります。

特定の数の並列クライアント要求の後、ブロッキングメソッドを使用するとパフォーマンスが大幅に低下します。

ただし、リアクティブ/ノンブロッキング方式では、リクエストの数に関係なく、一定のパフォーマンスが得られるはずです。

この記事では、 2つのRESTエンドポイントを実装します。1つはRestTemplateを使用し、もう1つはWebClientを使用します。彼らのタスクは、ツイートのリストを返す別の低速RESTWebサービスを呼び出すことです。

開始するには、Spring BootWebFluxスターター依存関係が必要です。

<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, username=@user1)
Tweet(text=WebClient is better, username=@user2)
Tweet(text=OK, both are useful, username=@user1)
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;
}

この場合、WebClientFluxパブリッシャーを返し、メソッドの実行が完了します。 結果が利用可能になると、パブリッシャーはサブスクライバーにツイートを送信し始めます。

この/tweets-non-blocking エンドポイントを呼び出すクライアント(この場合はWebブラウザー)も、返されたFluxオブジェクトにサブスクライブされることに注意してください。

今回はログを観察してみましょう。

Starting NON-BLOCKING Controller!
Exiting NON-BLOCKING Controller!
Tweet(text=RestTemplate rules, username=@user1)
Tweet(text=WebClient is better, username=@user2)
Tweet(text=OK, both are useful, username=@user1)

このエンドポイントメソッドは、応答を受信する前に完了していることに注意してください。

4. 結論

この記事では、SpringでWebクライアントを使用する2つの異なる方法について説明しました。

RestTemplateはJavaサーブレットAPIを使用しているため、同期してブロックしています。

逆に、 WebClient は非同期であり、応答が返されるのを待っている間、実行中のスレッドをブロックしません。 通知は、応答の準備ができたときにのみ生成されます。

RestTemplateは引き続き使用されます。 ただし、場合によっては、非ブロッキングアプローチは、ブロッキングアプローチと比較してはるかに少ないシステムリソースを使用します。 したがって、これらの場合はWebClientが推奨されます。

この記事で言及されているすべてのコードスニペットは、GitHubにあります。