ドキュメントのこの部分では、Netty、Undertow、Servlet 3.1+コンテナーなどのノンブロッキングサーバーで実行するために、リアクティブストリーム(英語) API上に構築されたリアクティブスタックWebアプリケーションのサポートについて説明します。個々の章は、Spring WebFluxフレームワーク、リアクティブ WebClient テストのサポート、およびリアクティブライブラリをカバーしています。サーブレットスタックWebアプリケーションについては、サーブレットスタック上のWebを参照してください。

1. Spring WebFlux

Spring Framework、Spring Web MVCに含まれる元のWebフレームワークは、サーブレットAPIおよびサーブレットコンテナー専用に作成されました。リアクティブスタックWebフレームワーク、Spring WebFluxは、バージョン5.0で後から追加されました。完全にノンブロッキングで、リアクティブストリーム(英語) バックプレッシャーをサポートし、Netty、Undertow、Servlet 3.1+コンテナーなどのサーバーで実行されます。

両方のWebフレームワークは、ソースモジュール( spring-webmvc(GitHub) およびspring-webflux(GitHub) )の名前を反映し、Spring Frameworkで共存します。各モジュールはオプションです。アプリケーションは、一方または他方のモジュール、または場合によっては両方を使用できます。たとえば、リアクティブ WebClientを備えたSpring MVCコントローラーです。

1.1. 概要

Spring WebFluxが作成されたのはなぜですか?

答えの一部は、少数のスレッドで同時実行性を処理し、より少ないハードウェアリソースでスケーリングするためのノンブロッキングWebスタックの必要性です。サーブレット3.1は、ノンブロッキングI/O用のAPIを提供していました。ただし、これを使用すると、契約が同期(Filter, Servlet)またはブロック(getParameter, getPart)であるサーブレットAPIの残りの部分から離れます。これが、ノンブロッキングランタイム全体の基盤として機能する新しい共通APIの動機でした。サーバー(Nettyなど)が非同期のノンブロッキングスペースで十分に確立されているため、これは重要です。

答えの他の部分は関数型プログラミングです。Java 5にアノテーションを追加すると機会(アノテーション付きRESTコントローラーや単体テストなど)が作成されるのと同様に、Java 8にラムダ式を追加するとJavaの関数APIの機会が作成されます。これは、非同期ロジックの宣言的な合成を可能にする、ノンブロッキングアプリケーションと継続スタイルAPI( CompletableFuture およびReactiveX(英語) によって普及した)の恩恵です。プログラミングモデルレベルでは、Java 8によりSpring WebFluxはアノテーション付きコントローラーとともに機能的なWebエンドポイントを提供できました。

1.1.1. 「リアクティブ」を定義する

「ノンブロッキング」と「機能」に触れましたが、リアクティブとはどういう意味でしょうか?

The term, “reactive,” refers to programming models that are built around reacting to change — network components reacting to I/O events, UI controllers reacting to mouse events, and others. In that sense, non-blocking is reactive, because, instead of being blocked, we are now in the mode of reacting to notifications as operations complete or data becomes available.

Springチームが「リアクティブ」に関連付けるもう1つの重要なメカニズムは、ノンブロッキングバックプレッシャーです。同期命令型コードでは、呼び出しをブロックすることは、呼び出し側を待機させるバックプレッシャーの自然な形として機能します。ノンブロッキングコードでは、高速プロデューサーがその宛先を圧倒しないように、イベントのレートを制御することが重要になります。

Reactive Streamsは、バックプレッシャーを伴う非同期コンポーネント間の相互作用を定義する小さな仕様です(GitHub) (Java 9でも採用 されています)。たとえば、データリポジトリ(パブリッシャー(英語) として機能)は、HTTPサーバー(サブスクライバー(英語) として機能)が応答に書き込むことができるデータを生成できます。Reactive Streamsの主な目的は、パブリッシャーがデータを生成する速さまたは遅さをサブスクライバーが制御できるようにすることです。

よくある質問: パブリッシャーがスローダウンできない場合はどうなりますか?
リアクティブストリームの目的は、メカニズムと境界を確立することだけです。パブリッシャーがスローダウンできない場合、バッファーするか、ドロップするか、失敗するかを決定する必要があります。

1.1.2. リアクティブAPI

リアクティブストリームは、相互運用性にとって重要なロールを果たします。ライブラリやインフラストラクチャコンポーネントには関心がありますが、低すぎるためアプリケーションAPIとしてはあまり有用ではありません。アプリケーションには、Java 8 Stream APIに似ていますが、コレクションだけでなく、非同期ロジックを構成するために、より高レベルでより関数APIが必要です。これは、リアクティブライブラリが果たすロールです。

Reactor(GitHub) は、Spring WebFluxに最適なリアクティブライブラリです。ReactiveX 演算子のボキャブラリー(英語) に合わせた豊富な演算子セットを通じて、0..1(Mono)および0..N(Flux)のデータシーケンスで動作する Mono (英語) および Flux (英語) APIタイプを提供します。Reactorはリアクティブストリームライブラリであるため、すべてのオペレーターがノンブロッキングバックプレッシャーをサポートしています。Reactorは、サーバー側のJavaに重点を置いています。Springとの緊密なコラボレーションで開発されます。

WebFluxは、コア依存としてReactorを必要としますが、Reactive Streamsを介して他のリアクティブライブラリと相互運用できます。一般的なルールとして、WebFlux APIは入力としてプレーン Publisher を受け入れ、それを内部でReactorタイプに適合させ、それを使用して、出力として Flux または Mono を返します。任意の Publisher を入力として渡すことができ、出力に操作を適用できますが、別のリアクティブライブラリで使用するために出力を調整する必要があります。可能な場合はいつでも(たとえば、アノテーション付きコントローラー)、WebFluxはRxJavaまたは別のリアクティブライブラリの使用に透過的に適応します。詳細については、リアクティブライブラリを参照してください。

Reactive APIに加えて、WebFluxはKotlinのコルーチン APIでも使用でき、より命令的なスタイルのプログラミングを提供します。次のKotlinコードサンプルは、Coroutines APIで提供されます。

1.1.3. プログラミングモデル

spring-web モジュールには、HTTP抽象化を含むSpring WebFlux、サポートされるサーバー用のReactive Streams アダプターコーデック、およびサーブレットAPIに匹敵するがノンブロッキング契約を含むコア WebHandler APIを含むリアクティブファウンデーションが含まれています。

その基盤で、Spring WebFluxは2つのプログラミングモデルの選択肢を提供します。

  • アノテーション付きコントローラー : Spring MVCと一致し、spring-web モジュールからの同じアノテーションに基づいています。Spring MVCコントローラーとWebFluxコントローラーは両方とも、リアクティブ(ReactorおよびRxJava)戻り型をサポートしているため、区別するのは簡単ではありません。注目すべき違いの1つは、WebFluxがリアクティブ @RequestBody 引数もサポートしていることです。

  • 機能エンドポイント : ラムダベースの軽量で機能的なプログラミングモデル。これは、アプリケーションがリクエストのルーティングと処理に使用できる小さなライブラリまたはユーティリティのセットと考えることができます。アノテーション付きコントローラーとの大きな違いは、アプリケーションが開始から終了までのリクエスト処理を担当することと、アノテーションを介してインテントを宣言してコールバックすることです。

1.1.4. 適用性

Spring MVCまたはWebFlux?

当然の質問ですが、不健全な二分法を設定します。実際、両方が連携して利用可能なオプションの範囲を拡大します。この2つは互いに連続性と一貫性を保つように設計されており、並べて利用できます。また、各側からのフィードバックは両側に役立ちます。次の図は、2つの関係、それらの共通点、それぞれが独自にサポートするものを示しています。

spring mvc and webflux venn

次の特定の点を考慮することをお勧めします。

  • 正常に動作するSpring MVCアプリケーションがある場合、変更する必要はありません。命令型プログラミングは、コードを記述、理解、およびデバッグする最も簡単な方法です。歴史的に、ほとんどがブロックしているため、ライブラリの選択肢は最大限にあります。

  • すでにノンブロッキングWebスタックを購入している場合、Spring WebFluxはこのスペースの他のユーザーと同じ実行モデルの利点を提供し、サーバー(Netty、Tomcat、Jetty、Undertow、およびServlet 3.1+コンテナー)の選択肢も提供します。プログラミングモデル(アノテーション付きコントローラーおよび機能的なWebエンドポイント)、およびリアクティブライブラリ(Reactor、RxJava、またはその他)の選択。

  • Java 8ラムダまたはKotlinで使用する軽量で機能的なWebフレームワークに興味がある場合は、Spring WebFlux関数Webエンドポイントを使用できます。また、透明性と制御性を高めることでメリットが得られる複雑な要件を持たない小規模なアプリケーションやマイクロサービスにも適しています。

  • マイクロサービスアーキテクチャでは、Spring MVCまたはSpring WebFluxコントローラーまたはSpring WebFlux機能エンドポイントのいずれかのアプリケーションを混在させることができます。両方のフレームワークで同じアノテーションベースのプログラミングモデルをサポートしているため、適切なジョブに適切なツールを選択しながら、知識を再利用しやすくなります。

  • アプリケーションを評価する簡単な方法は、依存関係をチェックすることです。使用するブロッキング永続性API(JPA、JDBC)またはネットワークAPIがある場合、Spring MVCは少なくとも一般的なアーキテクチャに最適です。ReactorとRxJavaの両方で別のスレッドでブロッキング呼び出しを実行することは技術的には可能ですが、ノンブロッキングWebスタックを最大限に活用することはできません。

  • リモートサービスを呼び出すSpring MVCアプリケーションがある場合は、リアクティブ WebClientを試してください。Spring MVCコントローラーメソッドから直接、リアクティブ型(Reactor、RxJava、またはその他 )を返すことができます。コールごとのレイテンシーまたはコール間の相互依存性が大きいほど、メリットは劇的になります。Spring MVCコントローラーは、他のリアクティブコンポーネントも呼び出すことができます。

  • 大規模なチームがある場合は、ノンブロッキング、機能的、宣言的プログラミングへの移行における急な学習曲線に留意してください。完全なスイッチなしで開始する実用的な方法は、リアクティブ WebClientを使用することです。それを超えて、小さく始めて、利益を測定します。幅広いアプリケーションでは、シフトは不要であると予想されます。どのような利点を探すべきかわからない場合は、ノンブロッキングI/Oの仕組み(たとえば、シングルスレッドNode.jsの同時実行)とその影響について学習することから始めてください。

1.1.5. サーバー

Spring WebFluxは、Tomcat、Jetty、サーブレット3.1+コンテナー、およびNettyやUndertowなどの非サーブレットランタイムでサポートされています。すべてのサーバーは低レベルの共通APIに適合しているため、サーバー全体で高レベルのプログラミングモデルをサポートできます。

Spring WebFluxには、サーバーを起動または停止するための組み込みサポートがありません。ただし、Spring構成とWebFluxインフラストラクチャからアプリケーションをアセンブルし、数行のコードで実行するのは簡単です。

Spring Bootには、これらのステップを自動化するWebFluxスターターがあります。デフォルトでは、スターターはNettyを使用しますが、MavenまたはGradleの依存関係を変更することにより、Tomcat、Jetty、またはUndertowに簡単に切り替えることができます。Spring BootのデフォルトはNettyです。これは、非同期のノンブロッキングスペースでより広く使用されており、クライアントとサーバーがリソースを共有できるためです。

TomcatとJettyは、Spring MVCとWebFluxの両方で使用できます。ただし、それらの使用方法は非常に異なることに注意してください。Spring MVCは、サーブレットのブロックI/Oに依存しており、必要に応じてアプリケーションがサーブレットAPIを直接使用できるようにします。Spring WebFluxは、サーブレット3.1ノンブロッキングI/Oに依存し、低レベルアダプターの背後にあるサーブレットAPIを使用し、直接使用には公開されません。

Undertowの場合、Spring WebFluxは、Servlet APIなしでUndertow APIを直接使用します。

1.1.6. パフォーマンス

パフォーマンスには多くの特性と意味があります。一般に、リアクティブでノンブロッキングは、アプリケーションの実行を高速化しません。場合によっては(たとえば、WebClient を使用してリモート呼び出しを並行して実行する場合)可能です。全体として、ノンブロッキング方式で物事を行うにはより多くの作業が必要であり、必要な処理時間をわずかに増やすことができます。

リアクティブでノンブロッキングの主な期待される利点は、少数の固定スレッドと少ないメモリで拡張できることです。予測可能な方法でスケーリングされるため、負荷がかかった場合でもアプリケーションの回復力が高まります。ただし、これらの利点を観察するには、ある程度の遅延(低速で予測不可能なネットワークI/Oの混在を含む)が必要です。それが、リアクティブスタックの強みを示し始めた場所であり、違いは劇的です。

1.1.7. 同時実行モデル

Spring MVCとSpring WebFluxは両方ともアノテーション付きコントローラーをサポートしますが、並行性モデルと、ブロッキングとスレッドのデフォルトの仮定に重要な違いがあります。

Spring MVC (サーブレットアプリケーション全般) では、アプリケーションが現在のスレッド (たとえば、リモート呼び出しの場合) をブロックできると想定されているため、サーブレットコンテナーはリクエスト処理中にブロックされる可能性を吸収するために大きなスレッドプールを使用します。

Spring WebFlux(および一般的なノンブロッキングサーバー)では、アプリケーションはブロックしないと想定されているため、ノンブロッキングサーバーは小さな固定サイズのスレッドプール(イベントループワーカー)を使用して要求を処理します。

「スケーリングする」と「少数のスレッド」は矛盾しているように聞こえるかもしれませんが、現在のスレッドをブロックしない(および代わりにコールバックに依存する)ことは、吸収するブロッキング呼び出しがないため、余分なスレッドが必要ないことを意味します。
ブロッキングAPIの呼び出し

ブロッキングライブラリを使用する必要がある場合はどうなりますか? ReactorとRxJavaは両方とも、異なるスレッドで処理を続行するために publishOn オペレーターを提供します。つまり、簡単な脱出ハッチがあります。ただし、ブロッキングAPIはこの同時実行モデルには適していません。

可変状態

ReactorおよびRxJavaでは、演算子を介してロジックを宣言し、実行時に、データが個別の段階で順次処理されるリアクティブパイプラインが形成されます。この主な利点は、パイプライン内のアプリケーションコードが同時に呼び出されることがないため、アプリケーションが可変状態を保護する必要がないことです。

スレッドモデル

Spring WebFluxで実行されているサーバーでどのスレッドを表示する必要がありますか?

  • 「vanilla」Spring WebFluxサーバー(たとえば、データアクセスやその他のオプションの依存関係なし)では、サーバー用に1つのスレッドと、リクエスト処理用にいくつかの他のスレッド(通常はCPUコアの数)が期待できます。ただし、サーブレット(ブロッキング)I/Oとサーブレット3.1(ノンブロッキング)I/Oの両方の使用をサポートするサーブレットコンテナーは、より多くのスレッド(たとえば、Tomcatで10)で開始する場合があります。

  • リアクティブ WebClient は、イベントループスタイルで動作します。それに関連する少数の固定数の処理スレッド(たとえば、Reactor Nettyコネクターを備えた reactor-http-nio- )を確認できます。ただし、Reactor Nettyがクライアントとサーバーの両方に使用される場合、2つはデフォルトでイベントループリソースを共有します。

  • ReactorおよびRxJavaは、スケジューラーと呼ばれるスレッドプールの抽象化を提供し、処理を別のスレッドプールに切り替えるために使用される publishOn オペレーターとともに使用します。スケジューラには、特定の並行性戦略を提案する名前があります。たとえば、「並列」(スレッド数が限られたCPUバインド作業の場合)または「弾性」(多数のスレッドを使用したI/Oバウンド作業の場合)です。このようなスレッドが表示される場合、特定のコードプール Scheduler 戦略を使用しているコードがあることを意味します。

  • データアクセスライブラリおよびその他のサードパーティの依存関係も、独自のスレッドを作成して使用できます。

設定

Spring Frameworkは、サーバーの起動と停止のサポートを提供しません。サーバーのスレッドモデルを構成するには、サーバー固有の構成APIを使用するか、Spring Bootを使用する場合は、各サーバーのSpring Boot構成オプションを確認する必要があります。 WebClient を直接構成できます。他のすべてのライブラリについては、それぞれのドキュメントを参照してください。

1.2. リアクティブコア

spring-web モジュールには、リアクティブWebアプリケーションに対する次の基本的なサポートが含まれています。

  • サーバー要求処理には、2つのレベルのサポートがあります。

    • HttpHandler : ノンブロッキングI/Oおよびリアクティブストリームのバックプレッシャーを伴うHTTPリクエスト処理の基本契約、およびReactor Netty、Undertow、Tomcat、Jetty、および任意のサーブレット3.1+コンテナー用のアダプター。

    • WebHandler API : 要求処理用のわずかに高いレベルの汎用Web API。その上に、アノテーション付きコントローラーや機能エンドポイントなどの具体的なプログラミングモデルが構築されます。

  • クライアント側には、Reactor Netty(GitHub) およびリアクティブJetty HttpClient(GitHub) のアダプターとともに、ノンブロッキングI/OおよびリアクティブストリームのバックプレッシャーでHTTPリクエストを実行する基本的な ClientHttpConnector 契約があります。アプリケーションで使用される上位レベルのWebClientは、この基本契約に基づいています。

  • クライアントおよびサーバーの場合、HTTPリクエストおよび応答コンテンツをシリアライズおよびデシリアライズするために使用するコーデック

1.2.1. HttpHandler

HttpHandler(Javadoc) は、要求と応答を処理する単一のメソッドを持つ単純な契約です。これは意図的に最小限であり、その主な唯一の目的は、異なるHTTPサーバーAPIを最小限に抽象化することです。

次の表に、サポートされているサーバーAPIを示します。

サーバー名使用されるサーバーAPIリアクティブストリームのサポート

Netty

Netty API

Reactor Netty(GitHub)

Undertow

Undertow API

spring-web: Undertow to Reactive Streamsブリッジ

Tomcat

サーブレット3.1ノンブロッキングI/O。ByteBuffersとbyte []を読み書きするTomcat API

spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

Jetty

サーブレット3.1ノンブロッキングI/O。ByteBuffersとbyte []を書き込むJetty API

spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

サーブレット3.1コンテナー

サーブレット3.1ノンブロッキングI/O

spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

次の表に、サーバーの依存関係を示します( サポートされるバージョン(GitHub) も参照)。

サーバー名グループ Id成果物の名前

Reactor Netty

io.projectreactor.netty

reactor-netty

Undertow

io.undertow

undertow-core

Tomcat

org.apache.tomcat.embed

tomcat-embed-core

Jetty

org.eclipse.jetty

桟橋サーバー、桟橋サーブレット

以下のコードスニペットは、各サーバーAPIで HttpHandler アダプターを使用することを示しています。

Reactor Netty

Java
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bind().block();
Kotlin
val handler: HttpHandler = ...
val adapter = ReactorHttpHandlerAdapter(handler)
HttpServer.create().host(host).port(port).handle(adapter).bind().block()

Undertow

Java
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
Kotlin
val handler: HttpHandler = ...
val adapter = UndertowHttpHandlerAdapter(handler)
val server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build()
server.start()

Tomcat

Java
HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);

Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/", "main");
server.setHost(host);
server.setPort(port);
server.start();
Kotlin
val handler: HttpHandler = ...
val servlet = TomcatHttpHandlerAdapter(handler)

val server = Tomcat()
val base = File(System.getProperty("java.io.tmpdir"))
val rootContext = server.addContext("", base.absolutePath)
Tomcat.addServlet(rootContext, "main", servlet)
rootContext.addServletMappingDecoded("/", "main")
server.host = host
server.setPort(port)
server.start()

Jetty

Java
HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);

Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();

ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();
Kotlin
val handler: HttpHandler = ...
val servlet = JettyHttpHandlerAdapter(handler)

val server = Server()
val contextHandler = ServletContextHandler(server, "")
contextHandler.addServlet(ServletHolder(servlet), "/")
contextHandler.start();

val connector = ServerConnector(server)
connector.host = host
connector.port = port
server.addConnector(connector)
server.start()

サーブレット3.1+コンテナー

WARとしてサーブレット3.1+コンテナーにデプロイするには、 AbstractReactiveWebInitializer (Javadoc) を継承してWARに含めることができます。そのクラスは、HttpHandlerServletHttpHandlerAdapter でラップし、Servletとして登録します。

1.2.2. WebHandler API

org.springframework.web.server パッケージは HttpHandler 契約に基づいており、複数の WebExceptionHandler (Javadoc) 、複数の WebFilter (Javadoc) 、および単一の WebHandler (Javadoc) コンポーネントのチェーンを介して要求を処理するための汎用Web APIを提供します。チェーンは、コンポーネントが自動検出される Spring ApplicationContext を指すか、コンポーネントをビルダーに登録するだけで、WebHttpHandlerBuilder と組み合わせることができます。

HttpHandler には、さまざまなHTTPサーバーの使用を抽象化するという単純なゴールがありますが、WebHandler APIは、次のようなWebアプリケーションで一般的に使用される広範な機能セットを提供することを目的としています。

  • 属性を持つユーザーセッション。

  • リクエスト属性。

  • リクエストの Locale または Principal を解決しました。

  • 解析およびキャッシュされたフォームデータへのアクセス。

  • マルチパートデータの抽象化。

  • さらに..

特別なBeanタイプ

以下の表は、WebHttpHandlerBuilder がSpring ApplicationContextで自動検出できるコンポーネント、または直接登録できるコンポーネントのリストです。

Bean 名Beanタイプカウント説明

<any>

WebExceptionHandler

0..N

WebFilter インスタンスのチェーンおよびターゲット WebHandlerからの例外の処理を提供します。詳細については、例外を参照してください。

<any>

WebFilter

0..N

フィルターチェーンの残りとターゲット WebHandlerの前後にインターセプトスタイルロジックを適用します。詳細については、フィルターを参照してください。

webHandler

WebHandler

1

リクエストのハンドラ。

webSessionManager

WebSessionManager

0..1

ServerWebExchangeのメソッドを介して公開される WebSession インスタンスのマネージャー。デフォルトでは DefaultWebSessionManager

serverCodecConfigurer

ServerCodecConfigurer

0..1

フォームデータとマルチパートデータを解析するための HttpMessageReader インスタンスへのアクセス用。このデータは、ServerWebExchangeのメソッドを通じて公開されます。デフォルトでは ServerCodecConfigurer.create()

localeContextResolver

LocaleContextResolver

0..1

ServerWebExchangeのメソッドを介して公開される LocaleContext のリゾルバー。デフォルトでは AcceptHeaderLocaleContextResolver

forwardedHeaderTransformer

ForwardedHeaderTransformer

0..1

転送されたタイプヘッダーを処理するには、抽出して削除するか、削除するだけです。デフォルトでは使用されません。

フォームデータ

ServerWebExchange は、フォームデータにアクセスするための次のメソッドを公開します。

Java
Mono<MultiValueMap<String, String>> getFormData();
Kotlin
suspend fun getFormData(): MultiValueMap<String, String>

DefaultServerWebExchange は、構成された HttpMessageReader を使用して、フォームデータ(application/x-www-form-urlencoded)を解析して MultiValueMapにします。デフォルトでは、FormHttpMessageReaderServerCodecConfigurer Beanが使用するように構成されています(WebハンドラーAPIを参照)。

マルチパートデータ

ServerWebExchange は、マルチパートデータにアクセスするための次のメソッドを公開します。

Java
Mono<MultiValueMap<String, Part>> getMultipartData();
Kotlin
suspend fun getMultipartData(): MultiValueMap<String, Part>

DefaultServerWebExchange は、構成された HttpMessageReader<MultiValueMap<String, Part>> を使用して、multipart/form-data コンテンツを MultiValueMapに解析します。現在、Synchronoss NIOマルチパート(GitHub) は、サポートされている唯一のサードパーティライブラリであり、マルチパートリクエストのノンブロッキング解析について知っている唯一のライブラリです。 ServerCodecConfigurer Beanを介して有効になります(WebハンドラーAPIを参照)。

ストリーミング形式でマルチパートデータを解析するには、代わりに HttpMessageReader<Part> から返された Flux<Part> を使用できます。例:アノテーション付きコントローラーでは、@RequestPart の使用は Map -likeが名前で個々のパーツにアクセスすることを意味するため、マルチパーツデータを完全に解析する必要があります。対照的に、@RequestBody を使用して、MultiValueMapに収集することなく、コンテンツを Flux<Part> にデコードできます。

転送されたヘッダー

リクエストがプロキシ(ロードバランサーなど)を通過すると、ホスト、ポート、スキームが変更される可能性があり、クライアントの観点からすると、正しいホスト、ポート、スキームを指すリンクを作成することが課題になります。

RFC 7239(英語) は、プロキシが元の要求に関する情報を提供するために使用できる Forwarded HTTPヘッダーを定義します。 X-Forwarded-Host, X-Forwarded-Port, X-Forwarded-Proto, X-Forwarded-SslX-Forwarded-Prefixなど、他の非標準ヘッダーもあります。

ForwardedHeaderTransformer は、転送されたヘッダーに基づいて要求のホスト、ポート、およびスキームを変更し、それらのヘッダーを削除するコンポーネントです。 forwardedHeaderTransformerという名前のBeanとして宣言でき、検出されて使用されます。

転送されたヘッダーには、意図したとおりにプロキシによってヘッダーが追加されたか、悪意のあるクライアントによってヘッダーが追加されたかをアプリケーションが認識できないため、セキュリティに関する考慮事項があります。これが、外部からの信頼できない転送トラフィックを削除するように、信頼の境界にあるプロキシを構成する必要がある理由です。 ForwardedHeaderTransformerremoveOnly=trueで構成することもできます。この場合、ヘッダーは削除されますが、使用されません。

5.1では、交換が作成される前に、転送されたヘッダーをより早く処理できるように、ForwardedHeaderFilter は非推奨となり、ForwardedHeaderTransformer に置き換えられました。フィルターが構成されている場合、フィルターのリストから除外され、代わりに ForwardedHeaderTransformer が使用されます。

1.2.3. フィルター

WebHandler APIでは、WebFilter を使用して、フィルターの残りの処理チェーンおよびターゲット WebHandlerの前後にインターセプションスタイルのロジックを適用できます。WebFlux構成を使用する場合、WebFilter の登録は、Spring Beanとして宣言し、(オプションで)Bean宣言で @Order を使用するか、Orderedを実装することによって優先順位を表すのと同じくらい簡単です。

CORS

Spring WebFluxは、コントローラーのアノテーションを通じてCORS構成のきめ細かなサポートを提供します。ただし、Spring Securityで使用する場合は、Spring Securityのチェーンフィルターよりも先に並べ替える必要があるビルトイン CorsFilterを使用することをお勧めします。

詳細については、CORSおよびCORS WebFilter のセクションを参照してください。

1.2.4. 例外

WebHandler APIでは、WebExceptionHandler を使用して、WebFilter インスタンスのチェーンおよびターゲット WebHandlerからの例外を処理できます。WebFlux構成を使用する場合、WebExceptionHandler の登録は、Spring Beanとして宣言し、(オプションで)Bean宣言で @Order を使用するか、Orderedを実装することによって優先順位を表すのと同じくらい簡単です。

次の表は、利用可能な WebExceptionHandler 実装を説明しています。

例外ハンドラー説明

ResponseStatusExceptionHandler

例外のHTTPステータスコードへの応答を設定することにより、タイプ ResponseStatusException (Javadoc) の例外の処理を提供します。

WebFluxResponseStatusExceptionHandler

@ResponseStatus アノテーションのHTTPステータスコードも例外で決定できる ResponseStatusExceptionHandler の拡張。

このハンドラーはWebFlux構成で宣言されています。

1.2.5. コーデック

spring-web および spring-core モジュールは、リアクティブストリームバックプレッシャーを使用したノンブロッキングI/Oを介して、上位レベルのオブジェクトとの間でバイトコンテンツを直列化および逆直列化するサポートを提供します。このサポートについて以下に説明します。

  • Encoder (Javadoc) および Decoder (Javadoc) は、HTTPに依存しないコンテンツをエンコードおよびデコードするための低レベルの契約です。

  • HttpMessageReader (Javadoc) および HttpMessageWriter (Javadoc) は、HTTPメッセージコンテンツをエンコードおよびデコードする契約です。

  • EncoderEncoderHttpMessageWriter でラップしてWebアプリケーションでの使用に適合させることができ、DecoderDecoderHttpMessageReaderでラップできます。

  • DataBuffer (Javadoc) は、さまざまなバイトバッファ表現(たとえば、Netty ByteBuf, java.nio.ByteBufferなど)を抽象化し、すべてのコーデックが動作するものです。このトピックの詳細については、「Springコア」セクションのデータバッファとコーデックを参照してください。

spring-core モジュールは、byte[], ByteBuffer, DataBuffer, Resourceおよび String エンコーダーとデコーダーの実装を提供します。 spring-web モジュールは、Jackson JSON、Jackson Smile、JAXB2、プロトコルバッファー、およびその他のエンコーダーとデコーダーに加えて、フォームデータ、マルチパートコンテンツ、サーバー送信イベントなどのWeb専用HTTPメッセージリーダーおよびライターの実装を提供します。

ClientCodecConfigurer および ServerCodecConfigurer は、通常、アプリケーションで使用するコーデックを構成およびカスタマイズするために使用されます。HTTPメッセージコーデックの構成に関するセクションを参照してください。

Jackson JSON

JSONとバイナリJSON(Smile(GitHub) )は両方とも、Jacksonライブラリが存在する場合にサポートされます。

Jackson2Decoder は次のように機能します。

  • Jacksonの非同期のノンブロッキングパーサーは、バイトチャンクのストリームを、それぞれJSONオブジェクトを表す TokenBufferに集約するために使用されます。

  • TokenBuffer はJacksonの ObjectMapper に渡され、より高いレベルのオブジェクトを作成します。

  • 単一値のパブリッシャー(例: Mono)にデコードする場合、1つの TokenBufferがあります。

  • 多値パブリッシャー(例: Flux)にデコードする場合、完全に形成されたオブジェクトに十分なバイトが受信されるとすぐに、各 TokenBufferObjectMapper に渡されます。入力コンテンツは、JSON配列、またはcontent-typeが「application/stream+json」の場合は行区切りのJSON(英語) です。

Jackson2Encoder は次のように機能します。

  • 単一の値のパブリッシャー(例: Mono)の場合、単に ObjectMapperを介して直列化します。

  • 「application/json」を使用する複数値パブリッシャーの場合、デフォルトで Flux#collectToList() を使用して値を収集し、結果のコレクションを直列化します。

  • application/stream+jsonapplication/stream+x-jackson-smileなどのストリーミングメディアタイプを持つ複数値パブリッシャーの場合、行区切りのJSON(英語) 形式を使用して各値を個別にエンコード、書き込み、フラッシュします。

  • SSEの場合、Jackson2Encoder はイベントごとに呼び出され、出力はフラッシュされ、遅延なく配信されます。

デフォルトでは、Jackson2EncoderJackson2Decoder は両方ともタイプ Stringの要素をサポートしていません。代わりに、デフォルトの仮定は、CharSequenceEncoderによってレンダリングされる直列化されたJSONコンテンツを表すストリングまたはストリングのシーケンスです。 Flux<String>からJSON配列をレンダリングする必要がある場合は、Flux#collectToList() を使用して Mono<List<String>>をエンコードします。

フォームデータ

FormHttpMessageReader および FormHttpMessageWriter は、「application/x-www-form-urlencoded」コンテンツのデコードおよびエンコードをサポートしています。

フォームコンテンツに複数の場所から頻繁にアクセスする必要があるサーバー側では、ServerWebExchange は、FormHttpMessageReader を介してコンテンツを解析し、繰り返しアクセスするために結果をキャッシュする専用の getFormData() メソッドを提供します。 WebHandler APIセクションのフォームデータを参照してください。

getFormData() を使用すると、元の生のコンテンツをリクエスト本文から読み取ることはできなくなります。このため、キャッシュされたフォームデータへのアクセスと生のリクエスト本文からの読み取りでは、アプリケーションは ServerWebExchange を一貫して通過することが期待されます。

マルチパート

MultipartHttpMessageReader および MultipartHttpMessageWriter は、「multipart / form-data」コンテンツのデコードおよびエンコードをサポートしています。 MultipartHttpMessageReader は、実際の解析のために Flux<Part> に別の HttpMessageReader に委譲し、単純にパーツを収集して MultiValueMapにします。現在、Synchronoss NIOマルチパート(GitHub) は実際の解析に使用されています。

マルチパートフォームコンテンツに複数の場所からアクセスする必要があるサーバー側では、ServerWebExchange は、MultipartHttpMessageReader を介してコンテンツを解析し、繰り返しアクセスするために結果をキャッシュする専用の getMultipartData() メソッドを提供します。 WebHandler APIセクションのマルチパートデータを参照してください。

getMultipartData() を使用すると、元の生のコンテンツをリクエスト本文から読み取ることはできなくなります。このため、アプリケーションは、パーツへのマップのような繰り返しアクセスのために getMultipartData() を一貫して使用するか、Flux<Part>への1回限りのアクセスのために SynchronossPartHttpMessageReader に依存する必要があります。

制限

入力ストリームの一部またはすべてをバッファリングするDecoder および HttpMessageReader 実装は、メモリにバッファリングする最大バイト数の制限を設定できます。入力が集約されて単一のオブジェクト(たとえば、@RequestBody byte[], x-www-form-urlencoded データを使用するコントローラーメソッドなど)として表されるため、バッファリングが発生する場合があります。バッファリングは、入力ストリームを分割するときに、ストリーミングでも発生する可能性があります。たとえば、区切りテキスト、JSONオブジェクトのストリームなどです。これらのストリーミングの場合、制限はストリーム内の1つのオブジェクトに関連付けられたバイト数に適用されます。

バッファサイズを設定するには、指定された Decoder または HttpMessageReadermaxInMemorySize プロパティを公開しているかどうかを確認し、公開されている場合はJavadocにデフォルト値に関する詳細が含まれます。WebFluxでは、ServerCodecConfigurer は、デフォルトコーデックの maxInMemorySize プロパティを介して、すべてのコーデックを設定する単一の場所を提供します。クライアント側では、WebClient.Builderで制限を変更できます。

マルチパート解析の場合、maxInMemorySize プロパティは非ファイルパーツのサイズを制限します。ファイルパーツの場合、パーツがディスクに書き込まれるしきい値を決定します。ディスクに書き込まれるファイルパーツには、追加の maxDiskUsagePerPart プロパティがあり、パーツあたりのディスク容量を制限します。マルチパートリクエスト内のパーツの総数を制限する maxParts プロパティもあります。WebFluxで3つすべてを構成するには、事前に構成された MultipartHttpMessageReader のインスタンスを ServerCodecConfigurerに提供する必要があります。

ストリーミング

HTTPレスポンス( text/event-stream, application/stream+jsonなど)にストリーミングする場合、切断されたクライアントを後よりも早く確実に検出するために、定期的にデータを送信することが重要です。このような送信は、コメントのみの空のSSEイベント、またはハートビートとして効果的に機能する他の「no-op」データです。

DataBuffer

DataBuffer は、WebFluxのバイトバッファーの表現です。参照のSpringコア部分は、データバッファとコーデックのセクションでさらに詳しく説明しています。理解すべき重要な点は、Nettyのような一部のサーバーでは、バイトバッファーがプールされ、参照カウントされ、メモリリークを避けるために消費されたときに解放する必要があるということです。

WebFluxアプリケーションは、コーデックに依存してより高いレベルのオブジェクトとの間で変換するのではなく、データバッファーを直接消費または生成しない限り、またはカスタムコーデックを作成することを選択しない限り、一般にそのような課題を考慮する必要はありません。そのような場合は、データバッファとコーデックの情報、特にDataBufferを使用するセクションを確認してください。

1.2.6. ロギング

SpringのデバッグレベルのロギングWebFluxは、コンパクトで、最小限で、人間に優しいように設計されています。特定の課題をデバッグするときにのみ役立つ他の情報と比較して、何度も何度も役立つ情報の価値の高いビットに焦点を当てています。

TRACEレベルのログ記録は、一般にDEBUGと同じ原則に従います(たとえば、ファイアホースであってはなりません)が、あらゆる課題のデバッグに使用できます。さらに、一部のログメッセージでは、TRACEとDEBUGで詳細レベルが異なる場合があります。

適切なログは、ログを使用した経験から得られます。記載されているゴールを達成できないものを見つけた場合は、お知らせください。

ログID

WebFluxでは、単一の要求を複数のスレッドで実行できます。スレッドIDは、特定の要求に属するログメッセージの関連付けには役立ちません。これが、デフォルトでWebFluxログメッセージの先頭にリクエスト固有のIDが付いている理由です。

サーバー側では、ログIDは ServerWebExchange 属性( LOG_ID_ATTRIBUTE (Javadoc) )に格納されますが、そのIDに基づいて完全にフォーマットされたプレフィックスは ServerWebExchange#getLogPrefix()から利用できます。 WebClient 側では、ログIDは ClientRequest 属性( LOG_ID_ATTRIBUTE (Javadoc) )に保存されますが、完全にフォーマットされたプレフィックスは ClientRequest#logPrefix()から入手できます。

機密データ

DEBUG および TRACE ロギングは、機密情報を記録できます。これが、フォームパラメータとヘッダーがデフォルトでマスクされる理由であり、それらのロギングを完全に明示的に有効にする必要があります。

次の例は、サーバー側の要求に対してこれを行う方法を示しています。

Java
@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().enableLoggingRequestDetails(true);
    }
}
Kotlin
@Configuration
@EnableWebFlux
class MyConfig : WebFluxConfigurer {

    override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
        configurer.defaultCodecs().enableLoggingRequestDetails(true)
    }
}

次の例は、クライアント側の要求に対してこれを行う方法を示しています。

Java
Consumer<ClientCodecConfigurer> consumer = configurer ->
        configurer.defaultCodecs().enableLoggingRequestDetails(true);

WebClient webClient = WebClient.builder()
        .exchangeStrategies(strategies -> strategies.codecs(consumer))
        .build();
Kotlin
val consumer: (ClientCodecConfigurer) -> Unit  = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) }

val webClient = WebClient.builder()
        .exchangeStrategies({ strategies -> strategies.codecs(consumer) })
        .build()
カスタムコーデック

アプリケーションは、追加のメディアタイプをサポートするカスタムコーデック、またはデフォルトコーデックでサポートされていない特定の動作を登録できます。

開発者によって表された一部の構成オプションは、デフォルトのコーデックに適用されます。カスタムコーデックは、バッファリング制限の強制や機密データのログ記録など、これらの設定に合わせて調整する機会を得たい場合があります

次の例は、クライアント側の要求に対してこれを行う方法を示しています。

Java
WebClient webClient = WebClient.builder()
        .codecs(configurer -> {
                CustomDecoder decoder = new CustomDecoder();
                configurer.customCodecs().registerWithDefaultConfig(decoder);
        })
        .build();
Kotlin
val webClient = WebClient.builder()
        .codecs({ configurer ->
                val decoder = CustomDecoder()
                configurer.customCodecs().registerWithDefaultConfig(decoder)
         })
        .build()

1.3. DispatcherHandler

Spring WebFluxは、Spring MVCと同様に、フロントコントローラーパターンを中心に設計されており、中央の WebHandlerである DispatcherHandlerが要求処理の共有アルゴリズムを提供し、実際の作業は構成可能なデリゲートコンポーネントによって実行されます。このモデルは柔軟性があり、多様なワークフローをサポートします。

DispatcherHandler は、Spring構成から必要なデリゲートコンポーネントを検出します。また、Spring Bean自体になるように設計されており、実行されるコンテキストにアクセスするために ApplicationContextAware を実装しています。 DispatcherHandlerwebHandlerというBean名で宣言されている場合、 WebHandler APIに従って、要求処理チェーンをまとめる WebHttpHandlerBuilder (Javadoc) によって検出されます。

WebFluxアプリケーションのSpring構成には通常、次のものが含まれます。

次の例が示すように、処理はチェーンを構築するために WebHttpHandlerBuilder に与えられます。

Java
ApplicationContext context = ...
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build();
Kotlin
val context: ApplicationContext = ...
val handler = WebHttpHandlerBuilder.applicationContext(context).build()

結果の HttpHandler は、サーバーアダプターで使用する準備ができています。

1.3.1. 特別なBeanタイプ

DispatcherHandler は、要求を処理して適切な応答をレンダリングするために特別なBeanに委譲します。「特別なBean」とは、WebFluxフレームワーク契約を実装するSpring管理の Object インスタンスを意味します。通常、これらにはビルトイン契約が付属していますが、プロパティをカスタマイズしたり、拡張したり、置き換えたりすることができます。

次の表に、DispatcherHandlerによって検出された特別なBeanを示します。下位レベルで検出された他のBeanもいくつかあることに注意してください(WebハンドラーAPIの特別なBeanタイプを参照)。

Beanタイプ説明

HandlerMapping

要求をハンドラーにマップします。マッピングはいくつかの基準に基づいており、その詳細は HandlerMapping の実装によって異なります。アノテーション付きコントローラー、単純なURLパターンマッピングなど。

主な HandlerMapping 実装は、@RequestMapping アノテーション付きメソッドの RequestMappingHandlerMapping、機能エンドポイントルートの RouterFunctionMapping、およびURIパスパターンと WebHandler インスタンスの明示的な登録の SimpleUrlHandlerMapping です。

HandlerAdapter

ハンドラーが実際に呼び出される方法に関係なく、DispatcherHandler が要求にマップされたハンドラーを呼び出すのに役立ちます。例:アノテーション付きコントローラーを呼び出すには、アノテーションを解決する必要があります。 HandlerAdapter の主な目的は、そのような詳細から DispatcherHandler を保護することです。

HandlerResultHandler

ハンドラー呼び出しからの結果を処理し、応答を確定します。結果処理を参照してください。

1.3.2. WebFlux構成

アプリケーションは、要求の処理に必要なインフラストラクチャBean(WebハンドラーAPIおよび DispatcherHandler にリストされている)を宣言できます。ただし、ほとんどの場合、WebFlux構成が最適な出発点です。必要なBeanを宣言し、それをカスタマイズするための高レベルの構成コールバックAPIを提供します。

Spring BootはWebFlux構成に依存してSpring WebFluxを構成し、さらに多くの便利なオプションを提供します。

1.3.3. 処理

DispatcherHandler は、要求を次のように処理します。

  • HandlerMapping は、一致するハンドラーを見つけるように求められ、最初の一致が使用されます。

  • ハンドラーが見つかった場合、適切な HandlerAdapterを介して実行され、実行からの戻り値が HandlerResultとして公開されます。

  • HandlerResult は適切な HandlerResultHandler に渡され、応答に直接書き込むか、ビューを使用してレンダリングすることで処理を完了します。

1.3.4. 結果処理

HandlerAdapterを介したハンドラーの呼び出しからの戻り値は、追加のコンテキストとともに HandlerResultとしてラップされ、そのサポートを要求する最初の HandlerResultHandler に渡されます。次の表に、利用可能な HandlerResultHandler 実装を示します。これらはすべてWebFlux構成で宣言されています。

結果ハンドラーのタイプ戻り値デフォルトの順序

ResponseEntityResultHandler

ResponseEntity、通常は @Controller インスタンスから。

0

ServerResponseResultHandler

ServerResponse、通常は機能エンドポイントから。

0

ResponseBodyResultHandler

@ResponseBody メソッドまたは @RestController クラスからの戻り値を処理します。

100

ViewResolutionResultHandler

CharSequence View (Javadoc) モデル(Javadoc) Mapレンダリング(Javadoc) 、またはその他の Object は、モデル属性として扱われます。

ビューリゾルバーも参照してください。

Integer.MAX_VALUE

1.3.5. 例外

HandlerAdapter から返された HandlerResult は、ハンドラー固有のメカニズムに基づいてエラー処理用の関数を公開できます。このエラー関数は次の場合に呼び出されます:

  • ハンドラー(たとえば、@Controller)呼び出しは失敗します。

  • HandlerResultHandler を介したハンドラー戻り値の処理は失敗します。

エラー関数は、ハンドラーから返されるリアクティブ型がデータ項目を生成する前にエラー信号が発生する限り、応答を(たとえば、エラー状況に)変更できます。

これにより、@Controller クラスの @ExceptionHandler メソッドがサポートされます。対照的に、Spring MVCでの同じサポートは HandlerExceptionResolverに基づいています。通常、これは重要ではありません。ただし、WebFluxでは、@ControllerAdvice を使用して、ハンドラーが選択される前に発生する例外を処理できないことに注意してください。

「アノテーション付きコントローラー」セクションの例外の管理またはWebHandler APIセクションの例外も参照してください。

1.3.6. ビューリゾルバー

ビューの解像度により、特定のビューテクノロジに縛られることなく、HTMLテンプレートとモデルを使用してブラウザにレンダリングできます。Spring WebFluxでは、ViewResolver インスタンスを使用してストリング(論理ビュー名を表す)を View インスタンスにマップする専用HandlerResultHandlerを介して、ビューの解像度がサポートされています。次に、View を使用して応答をレンダリングします。

取り扱い

ViewResolutionResultHandler に渡される HandlerResult には、ハンドラーからの戻り値と、要求処理中に追加された属性を含むモデルが含まれます。戻り値は次のいずれかとして処理されます。

  • String, CharSequence : 構成された ViewResolver 実装のリストを通じて View に解決される論理ビュー名。

  • void : 要求パスに基づいて、先頭と末尾のスラッシュを除いたデフォルトのビュー名を選択し、Viewに解決します。ビュー名が指定されなかった場合(モデル属性が返された場合など)、または非同期の戻り値(たとえば、空の Mono が完了した場合)でも同じことが起こります。

  • レンダリング(Javadoc) : ビュー解決シナリオのAPI。コード補完を使用してIDEのオプションを調べましょう。

  • Model, Map : リクエストのモデルに追加される追加のモデル属性。

  • Any other: Any other return value (except for simple types, as determined by BeanUtils#isSimpleProperty(Javadoc) ) is treated as a model attribute to be added to the model. The attribute name is derived from the class name by using conventions(Javadoc) , unless a handler method @ModelAttribute annotation is present.

モデルには、非同期のリアクティブタイプを含めることができます(たとえば、ReactorまたはRxJavaから)。レンダリングの前に、AbstractView はそのようなモデル属性を具体的な値に解決し、モデルを更新します。単一値のリアクティブ型は単一の値または値なし(空の場合)に解決され、複数値のリアクティブ型(たとえば Flux<T>)は収集されて List<T>に解決されます。

ビューの解像度を構成するには、ViewResolutionResultHandler BeanをSpring構成に追加するだけです。WebFlux構成は、ビューの解像度専用の構成APIを提供します。

Spring WebFluxと統合されたビューテクノロジーの詳細については、ビューテクノロジーを参照してください。

リダイレクト

ビュー名の特別な redirect: プレフィックスを使用すると、リダイレクトを実行できます。 UrlBasedViewResolver (およびサブクラス)は、これをリダイレクトが必要な命令として認識します。ビュー名の残りはリダイレクトURLです。

最終的な効果は、コントローラーが RedirectView または Rendering.redirectTo("abc").build()を返した場合と同じですが、コントローラー自体が論理ビュー名の観点から動作できるようになりました。 redirect:/some/resource などのビュー名は現在のアプリケーションに関連していますが、redirect:https://example.com/arbitrary/path などのビュー名は絶対URLにリダイレクトされます。

コンテンツ交渉

ViewResolutionResultHandler はコンテンツネゴシエーションをサポートしています。要求メディアタイプと、選択した各 Viewでサポートされているメディアタイプを比較します。要求されたメディアタイプをサポートする最初の View が使用されます。

JSONやXMLなどのメディアタイプをサポートするために、Spring WebFluxは HttpMessageWriterViewを提供します。これは、HttpMessageWriterを介してレンダリングする特別な View です。通常、これらはWebFluxの設定を介してデフォルトビューとして設定します。要求されたメディアタイプに一致する場合、デフォルトビューが常に選択され、使用されます。

1.4. アノテーション付きコントローラー

Spring WebFluxは、アノテーションベースのプログラミングモデルを提供します。@Controller および @RestController コンポーネントは、アノテーションを使用して、リクエストマッピング、リクエスト入力、例外処理などを表現します。アノテーション付きコントローラーには柔軟なメソッドシグネチャーがあり、基本クラスを継承したり、特定のインターフェースを実装したりする必要はありません。

次のリストは、基本的な例を示しています。

Java
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String handle() {
        return "Hello WebFlux";
    }
}
Kotlin
@RestController
class HelloController {

    @GetMapping("/hello")
    fun handle() = "Hello WebFlux"
}

上記の例では、メソッドは応答本体に書き込まれる String を返します。

1.4.1. @Controller

標準のSpring Bean定義を使用して、コントローラーBeanを定義できます。 @Controller ステレオタイプは自動検出を可能にし、クラスパス内の @Component クラスを検出し、それらのBean定義を自動登録するためのSpring一般サポートと連携しています。また、アノテーション付きクラスのステレオタイプとしても機能し、Webコンポーネントとしてのロールを示します。

そのような @Controller Beanの自動検出を有効にするには、次の例に示すように、Java構成にコンポーネントスキャンを追加できます。

Java
@Configuration
@ComponentScan("org.example.web") (1)
public class WebConfig {

    // ...
}
1 org.example.web パッケージをスキャンします。
Kotlin
@Configuration
@ComponentScan("org.example.web") (1)
class WebConfig {

    // ...
}
1 org.example.web パッケージをスキャンします。

@RestController は、それ自体が @Controller および @ResponseBodyでメタアノテーションされた合成アノテーションであり、すべてのメソッドが型レベルの @ResponseBody アノテーションを継承するコントローラーを示します。HTMLテンプレートを使用して、ビューの解像度とレンダリングに対して応答本文に直接書き込みます。

1.4.2. リクエストマッピング

@RequestMapping アノテーションは、リクエストをコントローラーメソッドにマップするために使用されます。URL、HTTPメソッド、要求パラメーター、ヘッダー、およびメディアタイプで一致するさまざまな属性があります。クラスレベルで使用して共有マッピングを表現したり、メソッドレベルで使用して特定のエンドポイントマッピングに絞り込んだりできます。

@RequestMappingのHTTPメソッド固有のショートカットバリアントもあります。

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

ほとんどのコントローラーメソッドは、デフォルトですべてのHTTPメソッドに一致する @RequestMappingを使用するのではなく、特定のHTTPメソッドにマップされる必要があるため、上記のアノテーションはカスタムアノテーションです。同時に、共有マッピングを表現するには、クラスレベルで @RequestMapping が必要です。

次の例では、タイプおよびメソッドレベルのマッピングを使用しています。

Java
@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}
Kotlin
@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    fun getPerson(@PathVariable id: Long): Person {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun add(@RequestBody person: Person) {
        // ...
    }
}
URIパターン

globパターンとワイルドカードを使用してリクエストをマッピングできます:

  • ? は1文字に一致する

  • * は、パスセグメント内の0個以上の文字に一致する

  • ** は0個以上のパスセグメントと一致する

次の例に示すように、URI変数を宣言し、@PathVariableを使用してそれらの値にアクセスすることもできます。

Java
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}
Kotlin
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
    // ...
}

次の例に示すように、クラスおよびメソッドレベルでURI変数を宣言できます。

Java
@Controller
@RequestMapping("/owners/{ownerId}") (1)
public class OwnerController {

    @GetMapping("/pets/{petId}") (2)
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}
1クラスレベルのURIマッピング。
2メソッドレベルのURIマッピング。
Kotlin
@Controller
@RequestMapping("/owners/{ownerId}") (1)
class OwnerController {

    @GetMapping("/pets/{petId}") (2)
    fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
        // ...
    }
}
1クラスレベルのURIマッピング。
2メソッドレベルのURIマッピング。

URI変数は自動的に適切な型に変換されるか、TypeMismatchException が発生します。単純型(int, long, Dateなど)はデフォルトでサポートされており、他のデータ型のサポートを登録できます。型変換および DataBinder を参照してください。

URI変数には明示的に名前を付けることができます(たとえば、@PathVariable("customId"))が、名前が同じでデバッグ情報またはJava 8.の -parameters コンパイラフラグを使用してコードをコンパイルする場合は、その詳細を省略できます。

構文 {*varName} は、ゼロ個以上の残りのパスセグメントと一致するURI変数を宣言します。たとえば、/resources/{*path} はすべてのファイル /resources/ と一致し、"path" 変数は完全な相対パスをキャプチャーします。

The syntax {varName:regex} declares a URI variable with a regular expression that has the syntax: {varName:regex}。For example, given a URL of /spring-web-3.0.5 .jar , the following method extracts the name, version, and file extension:

Java
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
    // ...
}
Kotlin
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable version: String, @PathVariable ext: String) {
    // ...
}

URIパスパターンには、起動時に PropertyPlaceHolderConfigurer を介してローカル、システム、環境、およびその他のプロパティソースに対して解決される ${…​} プレースホルダーを埋め込むこともできます。これを使用して、たとえば、外部設定に基づいてベースURLをパラメーター化できます。

Spring WebFluxは、URIパス一致サポートのために PathPattern および PathPatternParser を使用します。両方のクラスは spring-web にあり、実行時に多数のURIパスパターンが一致するWebアプリケーションのHTTP URLパスで使用するために特別に設計されています。

Spring WebFlux does not support suffix pattern matching — unlike Spring MVC, where a mapping such as /person also matches to /person.* . For URL-based content negotiation, if needed, we recommend using a query parameter, which is simpler, more explicit, and less vulnerable to URL path based exploits.

パターン比較

複数のパターンがURLに一致する場合、比較して最適な一致を見つける必要があります。これは、より具体的なパターンを探す PathPattern.SPECIFICITY_COMPARATORで行われます。

すべてのパターンについて、URI変数とワイルドカードの数に基づいてスコアが計算されます。URI変数のスコアはワイルドカードよりも低くなります。合計スコアの低いパターンが優先されます。2つのパターンのスコアが同じ場合、長い方が選択されます。

キャッチオールパターン( **, {*varName}など)はスコアリングから除外され、代わりに常に最後にソートされます。2つのパターンが両方とも包括的である場合、長い方が選択されます。

消費可能なメディアタイプ

次の例に示すように、リクエストの Content-Type に基づいてリクエストマッピングを絞り込むことができます。

Java
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
    // ...
}
Kotlin
@PostMapping("/pets", consumes = ["application/json"])
fun addPet(@RequestBody pet: Pet) {
    // ...
}

The consumes attribute also supports negation expressions — for example, !text/plain means any content type other than text/plain .

クラスレベルで共有 consumes 属性を宣言できます。ただし、他のほとんどの要求マッピング属性とは異なり、クラスレベルで使用する場合、メソッドレベルの consumes 属性は、クラスレベルの宣言を継承するのではなくオーバーライドします。

MediaType provides constants for commonly used media types — for example, APPLICATION_JSON_VALUE and APPLICATION_XML_VALUE .
作成可能なメディアタイプ

次の例に示すように、Accept 要求ヘッダーとコントローラーメソッドが生成するコンテンツタイプのリストに基づいて、要求マッピングを絞り込むことができます。

Java
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}
Kotlin
@GetMapping("/pets/{petId}", produces = ["application/json"])
@ResponseBody
fun getPet(@PathVariable String petId): Pet {
    // ...
}

メディアタイプは文字セットを指定できます。否定表現がサポートされています。たとえば、!text/plaintext/plain以外のコンテンツタイプを意味します。

クラスレベルで共有 produces 属性を宣言できます。ただし、他のほとんどの要求マッピング属性とは異なり、クラスレベルで使用する場合、メソッドレベルの produces 属性は、クラスレベルの宣言を継承するのではなくオーバーライドします。

MediaType provides constants for commonly used media types — e.g. APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE .
パラメータとヘッダー

クエリパラメータの条件に基づいてリクエストマッピングを絞り込むことができます。クエリパラメータの存在(myParam)、その不在(!myParam)、または特定の値(myParam=myValue)をテストできます。次の例では、値を持つパラメーターをテストします。

Java
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}
1 myParammyValueと等しいことを確認してください。
Kotlin
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
    // ...
}
1 myParammyValueと等しいことを確認してください。

次の例に示すように、リクエストヘッダー条件でも同じように使用できます。

Java
@GetMapping(path = "/pets", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}
1 myHeadermyValueと等しいことを確認してください。
Kotlin
@GetMapping("/pets", headers = ["myHeader=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
    // ...
}
1 myHeadermyValueと等しいことを確認してください。
HTTP HEAD, OPTIONS

@GetMapping および @RequestMapping(method=HttpMethod.GET) は、要求マッピングの目的でHTTP HEADを透過的にサポートします。コントローラーのメソッドを変更する必要はありません。 HttpHandler サーバーアダプターに適用される応答ラッパーは、Content-Length ヘッダーが実際に応答に書き込まずに書き込まれたバイト数に設定されるようにします。

デフォルトでは、HTTP OPTIONSは、Allow 応答ヘッダーを、一致するURLパターンを持つすべての @RequestMapping メソッドにリストされているHTTPメソッドのリストに設定することによって処理されます。

HTTPメソッド宣言のない @RequestMapping の場合、Allow ヘッダーは GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONSに設定されます。コントローラーメソッドは、サポートされるHTTPメソッドを常に宣言する必要があります(たとえば、HTTPメソッド固有のバリアント— @GetMapping, @PostMappingなどを使用して)。

@RequestMapping メソッドを明示的にHTTP HEADおよびHTTP OPTIONSにマップできますが、これは一般的なケースでは必要ありません。

カスタムアノテーション

Spring WebFluxは、要求のマッピングに合成アノテーションの使用をサポートしています。これらは、それ自体が @RequestMapping でメタアノテーションされ、より狭い、より具体的な目的で @RequestMapping 属性のサブセット(またはすべて)を再宣言するように構成されたアノテーションです。

@GetMapping, @PostMapping, @PutMapping, @DeleteMappingおよび @PatchMapping は、構成されたアノテーションの例です。ほぼ間違いなく、ほとんどのコントローラーメソッドは、デフォルトですべてのHTTPメソッドに一致する @RequestMappingを使用するのではなく、特定のHTTPメソッドにマッピングされる必要があるためです。合成されたアノテーションの例が必要な場合は、それらがどのように宣言されているかを参照してください。

Spring WebFluxは、カスタムリクエストマッチングロジックを持つカスタムリクエストマッピング属性もサポートしています。これは、RequestMappingHandlerMapping のサブクラス化と getCustomMethodCondition メソッドのオーバーライドを必要とする、より高度なオプションです。カスタム属性を確認して、独自の RequestConditionを返すことができます。

明示的な登録

ハンドラーメソッドをプログラムで登録できます。これは、動的な登録や、異なるURLでの同じハンドラーの異なるインスタンスなどの高度なケースに使用できます。次の例は、その方法を示しています。

Java
@Configuration
public class MyConfig {

    @Autowired
    public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) (1)
            throws NoSuchMethodException {

        RequestMappingInfo info = RequestMappingInfo
                .paths("/user/{id}").methods(RequestMethod.GET).build(); (2)

        Method method = UserHandler.class.getMethod("getUser", Long.class); (3)

        mapping.registerMapping(info, handler, method); (4)
    }

}
1ターゲットハンドラーとコントローラーのハンドラーマッピングを挿入します。
2要求マッピングメタデータを準備します。
3ハンドラーメソッドを取得します。
4登録を追加します。
Kotlin
@Configuration
class MyConfig {

    @Autowired
    fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) { (1)

        val info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build() (2)

        val method = UserHandler::class.java.getMethod("getUser", Long::class.java) (3)

        mapping.registerMapping(info, handler, method) (4)
    }
}
1ターゲットハンドラーとコントローラーのハンドラーマッピングを挿入します。
2要求マッピングメタデータを準備します。
3ハンドラーメソッドを取得します。
4登録を追加します。

1.4.3. 処理メソッド

@RequestMapping ハンドラーメソッドには柔軟なシグネチャーがあり、サポートされているコントローラーメソッドの引数と戻り値の範囲から選択できます。

メソッド引数

次の表に、サポートされているコントローラーメソッドの引数を示します。

リアクティブ型(Reactor、RxJava、またはその他 )は、ブロッキングI/O(要求本体の読み取りなど)を解決する必要がある引数でサポートされています。これは説明列にマークされています。ブロッキングを必要としない引数では、リアクティブ型は想定されていません。

JDK 1.8の java.util.Optional は、required 属性(たとえば、@RequestParam, @RequestHeaderなど)を持つアノテーションと組み合わせてメソッド引数としてサポートされ、required=falseと同等です。

コントローラーメソッドの引数説明

ServerWebExchange

Access to the full ServerWebExchange  — container for the HTTP request and response, request and session attributes, checkNotModified methods, and others.

ServerHttpRequest, ServerHttpResponse

HTTPリクエストまたは応答へのアクセス。

WebSession

セッションへのアクセス。これは、属性が追加されない限り、新しいセッションの開始を強制しません。リアクティブ型をサポートします。

java.security.Principal

The currently authenticated user — possibly a specific Principal implementation class if known. Supports reactive types.

org.springframework.http.HttpMethod

リクエストのHTTPメソッド。

java.util.Locale

The current request locale, determined by the most specific LocaleResolver available — in effect, the configured LocaleResolver / LocaleContextResolver .

java.util.TimeZone + java.time.ZoneId

LocaleContextResolverによって決定される、現在のリクエストに関連付けられたタイムゾーン。

@PathVariable

URIテンプレート変数へのアクセス用。URIパターンを参照してください。

@MatrixVariable

URIパスセグメントの名前と値のペアへのアクセス用。行列変数を参照してください。

@RequestParam

サーブレット要求パラメーターへのアクセス用。パラメータ値は、宣言されたメソッド引数タイプに変換されます。 @RequestParam を参照してください。

Note that use of @RequestParam is optional — for example, to set its attributes. See “Any other argument” later in this table.

@RequestHeader

要求ヘッダーへのアクセス用。ヘッダー値は、宣言されたメソッド引数タイプに変換されます。 @RequestHeader を参照してください。

@CookieValue

クッキーへのアクセス用。Cookie値は、宣言されたメソッド引数タイプに変換されます。 @CookieValue を参照してください。

@RequestBody

HTTPリクエスト本文へのアクセス用。本文コンテンツは、HttpMessageReader インスタンスを使用して、宣言されたメソッド引数タイプに変換されます。リアクティブ型をサポートします。 @RequestBody を参照してください。

HttpEntity<B>

要求ヘッダーと本文へのアクセス用。本体は HttpMessageReader インスタンスで変換されます。リアクティブ型をサポートします。 HttpEntity を参照してください。

@RequestPart

multipart/form-data リクエストの一部へのアクセス用。リアクティブ型をサポートします。マルチパートコンテンツおよびマルチパートデータを参照してください。

java.util.Map, org.springframework.ui.Modelおよび org.springframework.ui.ModelMap

HTMLコントローラーで使用され、ビューレンダリングの一部としてテンプレートに公開されるモデルへのアクセス用。

@ModelAttribute

データバインディングと検証が適用されたモデル内の既存の属性(存在しない場合はインスタンス化)へのアクセス用。 @ModelAttribute および Model および DataBinder を参照してください。

Note that use of @ModelAttribute is optional — for example, to set its attributes. See “Any other argument” later in this table.

Errors, BindingResult

コマンドオブジェクト、つまり @ModelAttribute 引数の検証およびデータバインディングからのエラーへのアクセス用。 Errorsまたは BindingResult 引数は、検証されたメソッド引数の直後に宣言する必要があります。

SessionStatus +クラスレベル @SessionAttributes

フォーム処理の補完をマークするために、クラスレベルの @SessionAttributes アノテーションを介して宣言されたセッション属性のクリーンアップをトリガーします。詳細については、 @SessionAttributes を参照してください。

UriComponentsBuilder

現在のリクエストのホスト、ポート、スキーム、およびパスに関連するURLを準備します。URIリンクを参照してください。

@SessionAttribute

For access to any session attribute — in contrast to model attributes stored in the session as a result of a class-level @SessionAttributes declaration. See @SessionAttribute for more details.

@RequestAttribute

要求属性へのアクセス用。詳細については、 @RequestAttribute を参照してください。

その他の引数

メソッドの引数が上記のいずれにも一致しない場合、デフォルトでは、BeanUtils#isSimpleProperty(Javadoc) によって決定される単純型の場合は @RequestParam として、そうでない場合は @ModelAttributeとして解決されます。

戻り値

次の表に、サポートされているコントローラーメソッドの戻り値を示します。このようReactor、RxJava、などのライブラリからの反応タイプのことを注意または他の一般的にすべての戻り値のためにサポートされています。

コントローラーメソッドの戻り値説明

@ResponseBody

戻り値は、HttpMessageWriter インスタンスを介してエンコードされ、応答に書き込まれます。 @ResponseBody を参照してください。

HttpEntity<B>, ResponseEntity<B>

戻り値は、HTTPヘッダーを含む完全な応答を指定し、本文は HttpMessageWriter インスタンスを介してエンコードされ、応答に書き込まれます。 ResponseEntity を参照してください。

HttpHeaders

ヘッダーを含み、本文を含まない応答を返すため。

String

A view name to be resolved with ViewResolver instances and used together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method can also programmatically enrich the model by declaring a Model argument (described earlier).

View

A View instance to use for rendering together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method can also programmatically enrich the model by declaring a Model argument (described earlier).

java.util.Map, org.springframework.ui.Model

暗黙的なモデルに追加される属性。ビュー名はリクエストパスに基づいて暗黙的に決定されます。

@ModelAttribute

モデルに追加される属性。ビュー名はリクエストパスに基づいて暗黙的に決定されます。

@ModelAttribute はオプションです。この表の後の「その他の戻り値」を参照してください。

Rendering

モデルおよびビューのレンダリングシナリオ用のAPI。

void

void、おそらく非同期(たとえば Mono<Void>)、戻り型(または null 戻り値)を持つメソッドは、ServerHttpResponseServerWebExchange 引数、または @ResponseStatus アノテーションも持っている場合、応答を完全に処理したと見なされます。コントローラーが正のETagまたは lastModified タイムスタンプチェックを行った場合も同様です。// TODO:詳細については、コントローラーを参照してください。

上記のいずれにも当てはまらない場合、void 戻り型は、RESTコントローラーの「応答本文なし」またはHTMLコントローラーのデフォルトのビュー名選択を示すこともできます。

Flux<ServerSentEvent>, Observable<ServerSentEvent>または他のリアクティブ型

サーバー送信イベントを発行します。 ServerSentEvent ラッパーは、データのみを書き込む必要がある場合は省略できます(ただし、produces 属性を使用してマッピングで text/event-stream を要求または宣言する必要があります)。

その他の戻り値

戻り値が上記のいずれにも一致しない場合、デフォルトでは、ビュー名として、String または void (デフォルトのビュー名の選択が適用される)として、またはモデルに追加されるモデル属性として扱われます。BeanUtils#isSimpleProperty(Javadoc) で決定された単純型でない場合は、未解決のままになります。

型変換

引数が String以外として宣言されている場合、文字列ベースの要求入力を表す一部のアノテーション付きコントローラーメソッド引数(たとえば、@RequestParam, @RequestHeader, @PathVariable, @MatrixVariable、および @CookieValue)は型変換を必要とする場合があります。

このような場合、構成されたコンバーターに基づいて型変換が自動的に適用されます。デフォルトでは、単純型( int, long, Dateなど)がサポートされています。型変換は、WebDataBinder[mvc-ann-initbinder]を参照)または FormattersFormattingConversionService に登録(Springフィールドのフォーマットを参照)することによりカスタマイズできます。

行列変数

RFC 3986(英語) は、パスセグメントの名前と値のペアについて説明します。Spring WebFluxでは、Tim Berners-Leeによる「古い投稿」(英語) に基づく「マトリックス変数」と呼ばれますが、URIパスパラメーターとも呼ばれます。

Matrix variables can appear in any path segment, with each variable separated by a semicolon and multiple values separated by commas — for example, "/cars;color=red,green;year=2012" . Multiple values can also be specified through repeated variable names — for example, "color=red;color=green;color=blue" .

Spring MVCとは異なり、WebFluxでは、URL内のマトリックス変数の有無は要求マッピングに影響しません。つまり、URIコンテンツを使用して変数の内容をマスクする必要はありません。つまり、コントローラーメソッドからマトリックス変数にアクセスする場合は、マトリックス変数が予想されるパスセグメントにURI変数を追加する必要があります。次の例は、その方法を示しています。

Java
// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11
}
Kotlin
// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
fun findPet(@PathVariable petId: String, @MatrixVariable q: Int) {

    // petId == 42
    // q == 11
}

すべてのパスセグメントにマトリックス変数を含めることができるため、次の例に示すように、マトリックス変数がどのパス変数にあると予想されるかを明確にする必要がある場合があります。

Java
// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22
}
Kotlin
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
        @MatrixVariable(name = "q", pathVar = "ownerId") q1: Int,
        @MatrixVariable(name = "q", pathVar = "petId") q2: Int) {

    // q1 == 11
    // q2 == 22
}

次の例に示すように、マトリックス変数をオプションとして定義し、デフォルト値を指定できます。

Java
// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1
}
Kotlin
// GET /pets/42

@GetMapping("/pets/{petId}")
fun findPet(@MatrixVariable(required = false, defaultValue = "1") q: Int) {

    // q == 1
}

すべての行列変数を取得するには、次の例に示すように、MultiValueMapを使用します。

Java
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}
Kotlin
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
        @MatrixVariable matrixVars: MultiValueMap<String, String>,
        @MatrixVariable(pathVar="petId") petMatrixVars: MultiValueMap<String, String>) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}
@RequestParam

@RequestParam アノテーションを使用して、クエリパラメーターをコントローラーのメソッド引数にバインドできます。次のコードスニペットは使用方法を示しています。

Java
@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) { (1)
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...
}
1 @RequestParamを使用します。
Kotlin
import org.springframework.ui.set

@Controller
@RequestMapping("/pets")
class EditPetForm {

    // ...

    @GetMapping
    fun setupForm(@RequestParam("petId") petId: Int, model: Model): String { (1)
        val pet = clinic.loadPet(petId)
        model["pet"] = pet
        return "petForm"
    }

    // ...
}
1 @RequestParamを使用します。
サーブレットAPIの「リクエストパラメータ」の概念は、クエリパラメータ、フォームデータ、およびマルチパートを1つにまとめます。ただし、WebFluxでは、それぞれに ServerWebExchangeを介して個別にアクセスします。 @RequestParam はクエリパラメータのみにバインドしますが、データバインドを使用して、クエリパラメータ、フォームデータ、およびマルチパートをコマンドオブジェクトに適用できます。

@RequestParam アノテーションを使用するメソッドパラメータはデフォルトで必須ですが、@RequestParam の必須フラグを false に設定するか、java.util.Optional ラッパーで引数を宣言することにより、メソッドパラメータがオプションであることを指定できます。

ターゲットメソッドのパラメータタイプが Stringでない場合、タイプ変換が自動的に適用されます。[mvc-ann-typeconversion]を参照してください。

@RequestParam アノテーションが Map<String, String> または MultiValueMap<String, String> 引数で宣言されると、マップにはすべてのクエリパラメーターが入力されます。

Note that use of @RequestParam is optional — for example, to set its attributes. By default, any argument that is a simple value type (as determined by BeanUtils#isSimpleProperty(Javadoc) ) and is not resolved by any other argument resolver is treated as if it were annotated with @RequestParam .

@RequestHeader

@RequestHeader アノテーションを使用して、リクエストヘッダーをコントローラーのメソッド引数にバインドできます。

次の例は、ヘッダー付きのリクエストを示しています。

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

次の例は、Accept-Encoding および Keep-Alive ヘッダーの値を取得します。

Java
@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, (1)
        @RequestHeader("Keep-Alive") long keepAlive) { (2)
    //...
}
1 Accept-Encoging ヘッダーの値を取得します。
2 Keep-Alive ヘッダーの値を取得します。
Kotlin
@GetMapping("/demo")
fun handle(
        @RequestHeader("Accept-Encoding") encoding: String, (1)
        @RequestHeader("Keep-Alive") keepAlive: Long) { (2)
    //...
}
1 Accept-Encoging ヘッダーの値を取得します。
2 Keep-Alive ヘッダーの値を取得します。

ターゲットメソッドのパラメータタイプが Stringでない場合、タイプ変換が自動的に適用されます。[mvc-ann-typeconversion]を参照してください。

@RequestHeader アノテーションが Map<String, String>, MultiValueMap<String, String>または HttpHeaders 引数で使用される場合、マップにはすべてのヘッダー値が入力されます。

コンマ区切りの文字列を、文字列の配列またはコレクション、または型変換システムに既知の他の型に変換するための組み込みサポートが利用可能です。例: @RequestHeader("Accept") アノテーションが付けられたメソッドパラメーターは、タイプ String である場合がありますが、String[] または List<String>である場合もあります。
@CookieValue

@CookieValue アノテーションを使用して、HTTP Cookieの値をコントローラーのメソッド引数にバインドできます。

次の例は、Cookieを使用したリクエストを示しています。

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

次のコードサンプルは、Cookie値を取得する方法を示しています。

Java
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { (1)
    //...
}
1Cookie値を取得します。
Kotlin
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) { (1)
    //...
}
1Cookie値を取得します。

ターゲットメソッドのパラメータタイプが Stringでない場合、タイプ変換が自動的に適用されます。[mvc-ann-typeconversion]を参照してください。

@ModelAttribute

メソッドの引数で @ModelAttribute アノテーションを使用して、モデルの属性にアクセスしたり、存在しない場合はインスタンス化したりできます。また、モデル属性は、フィールド名に一致する名前を持つクエリパラメータとフォームフィールドの値でオーバーレイされます。これはデータバインディングと呼ばれ、個々のクエリパラメータとフォームフィールドの解析と変換に対処する必要がなくなります。次の例では、Petのインスタンスをバインドします。

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { } (1)
1 Petのインスタンスをバインドします。
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { } (1)
1 Petのインスタンスをバインドします。

上記の例の Pet インスタンスは、次のように解決されます。

  • Model を介して既に追加されている場合はモデルから。

  • HTTPセッションから @SessionAttributes を介して。

  • デフォルトのコンストラクターの呼び出しから。

  • クエリパラメータまたはフォームフィールドに一致する引数を持つ「プライマリコンストラクター」の呼び出しから。引数名は、JavaBeans @ConstructorProperties またはバイトコード内の実行時保持パラメーター名によって決定されます。

モデル属性インスタンスが取得された後、データバインディングが適用されます。 WebExchangeDataBinder クラスは、クエリパラメータとフォームフィールドの名前をターゲット Objectのフィールド名に一致させます。一致するフィールドは、必要に応じて型変換が適用された後に入力されます。データバインディング(および検証)の詳細については、検証を参照してください。データバインディングのカスタマイズの詳細については、 DataBinder を参照してください。

データバインディングはエラーになる可能性があります。デフォルトでは、WebExchangeBindException が発生しますが、コントローラーメソッドでこのようなエラーをチェックするには、次の例に示すように、@ModelAttributeのすぐ隣に BindingResult 引数を追加できます。

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { (1)
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
1 BindingResultを追加します。
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}
1 BindingResultを追加します。

javax.validation.Valid アノテーションまたはSpringの @Validated アノテーションを追加することにより、データバインディング後に検証を自動的に適用できます(Bean バリデーションおよびSpring検証も参照)。次の例では、@Valid アノテーションを使用しています。

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { (1)
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
1モデル属性引数で @Valid を使用します。
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}
1モデル属性引数で @Valid を使用します。

Spring WebFlux, unlike Spring MVC, supports reactive types in the model — for example, Mono<Account> or io.reactivex.Single<Account> . You can declare a @ModelAttribute argument with or without a reactive type wrapper, and it will be resolved accordingly, to the actual value if necessary. However, note that, to use a BindingResult argument, you must declare the @ModelAttribute argument before it without a reactive type wrapper, as shown earlier. Alternatively, you can handle any errors through the reactive type, as the following example shows:

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
    return petMono
        .flatMap(pet -> {
            // ...
        })
        .onErrorResume(ex -> {
            // ...
        });
}
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") petMono: Mono<Pet>): Mono<String> {
    return petMono
            .flatMap { pet ->
                // ...
            }
            .onErrorResume{ ex ->
                // ...
            }
}

Note that use of @ModelAttribute is optional — for example, to set its attributes. By default, any argument that is not a simple value type( as determined by BeanUtils#isSimpleProperty(Javadoc) ) and is not resolved by any other argument resolver is treated as if it were annotated with @ModelAttribute .

@SessionAttributes

@SessionAttributes は、リクエスト間で WebSession にモデル属性を保存するために使用されます。これは、特定のコントローラーが使用するセッション属性を宣言する型レベルのアノテーションです。これは通常、後続のアクセス要求のためにセッションに透過的に保存されるモデル属性の名前またはモデル属性のタイプをリストします。

次の例を考えてみましょう。

Java
@Controller
@SessionAttributes("pet") (1)
public class EditPetForm {
    // ...
}
1 @SessionAttributes アノテーションを使用します。
Kotlin
@Controller
@SessionAttributes("pet") (1)
class EditPetForm {
    // ...
}
1 @SessionAttributes アノテーションを使用します。

最初の要求で、名前が petのモデル属性がモデルに追加されると、自動的に WebSessionに昇格して保存されます。次の例に示すように、別のコントローラーメソッドが SessionStatus メソッド引数を使用してストレージをクリアするまで、そこに残ります。

Java
@Controller
@SessionAttributes("pet") (1)
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) { (2)
        if (errors.hasErrors()) {
            // ...
        }
            status.setComplete();
            // ...
        }
    }
}
1 @SessionAttributes アノテーションを使用します。
2 SessionStatus 変数を使用します。
Kotlin
@Controller
@SessionAttributes("pet") (1)
class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String { (2)
        if (errors.hasErrors()) {
            // ...
        }
        status.setComplete()
        // ...
    }
}
1 @SessionAttributes アノテーションを使用します。
2 SessionStatus 変数を使用します。
@SessionAttribute

グローバルに(つまり、コントローラーの外部で)管理される既存のセッション属性にアクセスする必要がある場合 — たとえば、フィルタによって)存在していても存在していなくても、次の例に示すように、メソッドパラメータで @SessionAttribute アノテーションを使用できます。

Java
@GetMapping("/")
public String handle(@SessionAttribute User user) { (1)
    // ...
}
1 @SessionAttributeを使用します。
Kotlin
@GetMapping("/")
fun handle(@SessionAttribute user: User): String { (1)
    // ...
}
1 @SessionAttributeを使用します。

セッション属性を追加または削除する必要があるユースケースの場合、WebSession をコントローラーメソッドに挿入することを検討してください。

コントローラーワークフローの一部としてセッションでモデル属性を一時的に保存するには、 @SessionAttributes に従って、SessionAttributesの使用を検討してください。

@RequestAttribute

@SessionAttributeと同様に、次の例に示すように、@RequestAttribute アノテーションを使用して、以前に作成された既存の要求属性にアクセスできます(たとえば、WebFilterによって)。

Java
@GetMapping("/")
public String handle(@RequestAttribute Client client) { (1)
    // ...
}
1 @RequestAttributeを使用します。
Kotlin
@GetMapping("/")
fun handle(@RequestAttribute client: Client): String { (1)
    // ...
}
1 @RequestAttributeを使用します。
マルチパートコンテンツ

マルチパートデータで説明したように、ServerWebExchange はマルチパートコンテンツへのアクセスを提供します。次の例に示すように、コントローラーでファイルアップロードフォーム(ブラウザーなどから)を処理する最良の方法は、コマンドオブジェクトへのデータバインディングを使用することです。

Java
class MyForm {

    private String name;

    private MultipartFile file;

    // ...

}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        // ...
    }

}
Kotlin
class MyForm(
        val name: String,
        val file: MultipartFile)

@Controller
class FileUploadController {

    @PostMapping("/form")
    fun handleFormUpload(form: MyForm, errors: BindingResult): String {
        // ...
    }

}

RESTfulサービスシナリオで、ブラウザ以外のクライアントからマルチパートリクエストを送信することもできます。次の例では、JSONとともにファイルを使用しています。

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
    "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

次の例に示すように、@RequestPartを使用して個々のパーツにアクセスできます。

Java
@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata, (1)
        @RequestPart("file-data") FilePart file) { (2)
    // ...
}
1 @RequestPart を使用してメタデータを取得します。
2 @RequestPart を使用してファイルを取得します。
Kotlin
@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata, (1)
        @RequestPart("file-data") FilePart file): String { (2)
    // ...
}
1 @RequestPart を使用してメタデータを取得します。
2 @RequestPart を使用してファイルを取得します。

To deserialize the raw part content (for example, to JSON — similar to @RequestBody ), you can declare a concrete target Object , instead of Part , as the following example shows:

Java
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { (1)
    // ...
}
1 @RequestPart を使用してメタデータを取得します。
Kotlin
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData): String { (1)
    // ...
}
1 @RequestPart を使用してメタデータを取得します。

@RequestPartjavax.validation.Valid またはSpringの @Validated アノテーションと組み合わせて使用できます。これにより、標準Bean検証が適用されます。検証エラーにより WebExchangeBindException が発生し、400(BAD_REQUEST)応答が発生します。例外には、エラーの詳細を含む BindingResult が含まれます。また、非同期ラッパーで引数を宣言し、エラー関連の演算子を使用することで、コントローラーメソッドで処理することもできます。

Java
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
    // use one of the onError* operators...
}
Kotlin
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
    // ...
}

すべてのマルチパートデータに MultiValueMapとしてアクセスするには、次の例に示すように、@RequestBodyを使用できます。

Java
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) { (1)
    // ...
}
1 @RequestBodyを使用します。
Kotlin
@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { (1)
    // ...
}
1 @RequestBodyを使用します。

次の例に示すように、ストリーミング形式でマルチパートデータに順次アクセスするには、代わりに @RequestBodyFlux<Part> (またはKotlinの Flow<Part> )を使用できます。

Java
@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) { (1)
    // ...
}
1 @RequestBodyを使用します。
Kotlin
@PostMapping("/")
fun handle(@RequestBody parts: Flow<Part>): String { (1)
    // ...
}
1 @RequestBodyを使用します。
@RequestBody

@RequestBody アノテーションを使用して、要求本体を読み取り、HttpMessageReaderを介して Object にデシリアライズすることができます。次の例では、@RequestBody 引数を使用しています。

Java
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
    // ...
}
Kotlin
@PostMapping("/accounts")
fun handle(@RequestBody account: Account) {
    // ...
}

Spring MVCとは異なり、WebFluxでは、@RequestBody メソッドの引数は、リアクティブ型、完全なノンブロッキング読み取り、および(クライアントからサーバーへの)ストリーミングをサポートします。

Java
@PostMapping("/accounts")
public void handle(@RequestBody Mono<Account> account) {
    // ...
}
Kotlin
@PostMapping("/accounts")
fun handle(@RequestBody accounts: Flow<Account>) {
    // ...
}

WebFlux構成HTTPメッセージコーデックオプションを使用して、メッセージリーダーを構成またはカスタマイズできます。

@RequestBodyjavax.validation.Valid またはSpringの @Validated アノテーションと組み合わせて使用できます。これにより、標準Bean検証が適用されます。検証エラーにより WebExchangeBindExceptionが発生し、400(BAD_REQUEST)応答が発生します。例外には、エラーの詳細を含む BindingResult が含まれており、非同期ラッパーで引数を宣言し、エラー関連の演算子を使用することにより、コントローラーメソッドで処理できます。

Java
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Mono<Account> account) {
    // use one of the onError* operators...
}
Kotlin
@PostMapping("/accounts")
fun handle(@Valid @RequestBody account: Mono<Account>) {
    // ...
}
HttpEntity

HttpEntity は、 @RequestBody を使用する場合とほぼ同じですが、要求ヘッダーと本文を公開するコンテナーオブジェクトに基づいています。次の例では、HttpEntityを使用しています。

Java
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}
Kotlin
@PostMapping("/accounts")
fun handle(entity: HttpEntity<Account>) {
    // ...
}
@ResponseBody

メソッドで @ResponseBody アノテーションを使用して、HttpMessageWriterを介して戻り値を応答本体に直列化できます。次の例は、その方法を示しています。

Java
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}
Kotlin
@GetMapping("/accounts/{id}")
@ResponseBody
fun handle(): Account {
    // ...
}

@ResponseBody はクラスレベルでもサポートされます。この場合、すべてのコントローラーメソッドによって継承されます。これは @RestControllerの効果であり、これは @Controller および @ResponseBodyでマークされたメタアノテーションにすぎません。

@ResponseBody はリアクティブ型をサポートします。つまり、Reactor型またはRxJava型を返し、それらが生成する非同期値を応答にレンダリングさせることができます。詳細については、ストリーミングおよびJSONレンダリングを参照してください。

@ResponseBody メソッドをJSON直列化ビューと組み合わせることができます。詳細については、Jackson JSONを参照してください。

WebFlux構成HTTPメッセージコーデックオプションを使用して、メッセージの書き込みを構成またはカスタマイズできます。

ResponseEntity

ResponseEntity @ResponseBody に似ていますが、ステータスとヘッダーがあります。例:

Java
@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok().eTag(etag).build(body);
}
Kotlin
@GetMapping("/something")
fun handle(): ResponseEntity<String> {
    val body: String = ...
    val etag: String = ...
    return ResponseEntity.ok().eTag(etag).build(body)
}

WebFluxは、単一値のリアクティブ型を使用して ResponseEntity を非同期的に生成すること、および/またはボディの単一値および複数値のリアクティブ型を使用することをサポートしています。

Jackson JSON

Springは、Jackson JSONライブラリのサポートを提供します。

JSONビュー

Spring WebFluxは、ObjectのすべてのフィールドのサブセットのみをレンダリングできるJacksonの直列化ビュー(英語) の組み込みサポートを提供します。 @ResponseBody または ResponseEntity コントローラーメソッドで使用するには、次の例に示すように、Jacksonの @JsonView アノテーションを使用して直列化ビュークラスをアクティブにします。

Java
@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}
Kotlin
@RestController
class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView::class)
    fun getUser(): User {
        return User("eric", "7!jd#h23")
    }
}

class User(
        @JsonView(WithoutPasswordView::class) val username: String,
        @JsonView(WithPasswordView::class) val password: String
) {
    interface WithoutPasswordView
    interface WithPasswordView : WithoutPasswordView
}
@JsonView ではビュークラスの配列を使用できますが、指定できるのはコントローラーメソッドごとに1つだけです。複数のビューをアクティブにする必要がある場合は、複合インターフェースを使用します。

1.4.4. Model

@ModelAttribute アノテーションを使用できます。

  • モデルからオブジェクトを作成またはアクセスし、WebDataBinderを介してリクエストにバインドする @RequestMapping メソッドのメソッド引数

  • @Controller または @ControllerAdvice クラスのメソッドレベルのアノテーションとして、@RequestMapping メソッドを呼び出す前にモデルを初期化できます。

  • @RequestMapping メソッドで、戻り値をモデル属性としてマークします。

このセクションでは、@ModelAttribute メソッド、または前述のリストの2番目の項目について説明します。コントローラーは、@ModelAttribute メソッドをいくつでも持つことができます。このようなメソッドはすべて、同じコントローラー内の @RequestMapping メソッドの前に呼び出されます。 @ModelAttribute メソッドは、@ControllerAdviceを介してコントローラー間で共有することもできます。詳細については、コントローラーのアドバイスのセクションを参照してください。

@ModelAttribute メソッドには、柔軟なメソッドシグネチャーがあります。これらは、@RequestMapping メソッドと同じ引数の多くをサポートします( @ModelAttribute 自体とリクエスト本文に関連するものを除く)。

次の例では、@ModelAttribute メソッドを使用しています。

Java
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountRepository.findAccount(number));
    // add more ...
}
Kotlin
@ModelAttribute
fun populateModel(@RequestParam number: String, model: Model) {
    model.addAttribute(accountRepository.findAccount(number))
    // add more ...
}

次の例では、1つの属性のみを追加します。

Java
@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}
Kotlin
@ModelAttribute
fun addAccount(@RequestParam number: String): Account {
    return accountRepository.findAccount(number);
}
名前が明示的に指定されていない場合、 Conventions (Javadoc) のjavadocに従って、タイプに基づいてデフォルト名が選択されます。オーバーロードされた addAttribute メソッドを使用するか、@ModelAttribute のname属性(戻り値用)を使用して、明示的な名前をいつでも割り当てることができます。

Spring WebFluxは、Spring MVCとは異なり、モデル内のリアクティブ型を明示的にサポートします(たとえば、Mono<Account> または io.reactivex.Single<Account>)。次の例に示すように、@ModelAttribute 引数がラッパーなしで宣言されている場合、そのような非同期モデル属性は、@RequestMapping 呼び出しの時点で実際の値に透過的に解決(およびモデル更新)できます。

Java
@ModelAttribute
public void addAccount(@RequestParam String number) {
    Mono<Account> accountMono = accountRepository.findAccount(number);
    model.addAttribute("account", accountMono);
}

@PostMapping("/accounts")
public String handle(@ModelAttribute Account account, BindingResult errors) {
    // ...
}
Kotlin
import org.springframework.ui.set

@ModelAttribute
fun addAccount(@RequestParam number: String) {
    val accountMono: Mono<Account> = accountRepository.findAccount(number)
    model["account"] = accountMono
}

@PostMapping("/accounts")
fun handle(@ModelAttribute account: Account, errors: BindingResult): String {
    // ...
}

さらに、リアクティブ型ラッパーを持つモデル属性は、ビューのレンダリングの直前に実際の値に解決されます(そしてモデルが更新されます)。

@ModelAttribute@RequestMapping メソッドのメソッドレベルのアノテーションとして使用することもできます。その場合、@RequestMapping メソッドの戻り値はモデル属性として解釈されます。戻り値がビュー名として解釈される String でない限り、これはHTMLコントローラーのデフォルトの動作であるため、通常は必要ありません。 @ModelAttribute は、次の例に示すように、モデル属性名のカスタマイズにも役立ちます。

Java
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
    // ...
    return account;
}
Kotlin
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
fun handle(): Account {
    // ...
    return account
}

1.4.5. DataBinder

@Controller または @ControllerAdvice クラスは、WebDataBinderのインスタンスを初期化するために、@InitBinder メソッドを持つことができます。これらは次の目的で使用されます。

  • 要求パラメーター(つまり、フォームデータまたはクエリ)をモデルオブジェクトにバインドします。

  • String ベースの要求値(要求パラメーター、パス変数、ヘッダー、Cookieなど)をコントローラーメソッド引数のターゲットタイプに変換します。

  • HTMLフォームをレンダリングするときに、モデルオブジェクト値を String 値としてフォーマットします。

@InitBinder メソッドは、コントローラー固有の java.bean.PropertyEditor またはSpring Converter および Formatter コンポーネントを登録できます。さらに、WebFlux Java構成を使用して、Converter および Formatter タイプをグローバルに共有される FormattingConversionServiceに登録できます。

@InitBinder メソッドは、@ModelAttribute (コマンドオブジェクト)引数を除き、@RequestMapping メソッドと同じ引数の多くをサポートします。通常、これらは、登録用の WebDataBinder 引数、および void 戻り値で宣言されます。次の例では、@InitBinder アノテーションを使用しています。

Java
@Controller
public class FormController {

    @InitBinder (1)
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}
1 @InitBinder アノテーションを使用します。
Kotlin
@Controller
class FormController {

    @InitBinder (1)
    fun initBinder(binder: WebDataBinder) {
        val dateFormat = SimpleDateFormat("yyyy-MM-dd")
        dateFormat.isLenient = false
        binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
    }

    // ...
}

または、共有 FormattingConversionServiceを介して Formatter ベースのセットアップを使用する場合、次の例に示すように、同じアプローチを再利用してコントローラー固有の Formatter インスタンスを登録できます。

Java
@Controller
public class FormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); (1)
    }

    // ...
}
1カスタムフォーマッタ(この場合は DateFormatter)を追加します。
Kotlin
@Controller
class FormController {

    @InitBinder
    fun initBinder(binder: WebDataBinder) {
        binder.addCustomFormatter(DateFormatter("yyyy-MM-dd")) (1)
    }

    // ...
}
1カスタムフォーマッタ(この場合は DateFormatter)を追加します。

1.4.6. 例外の管理

@Controller および@ControllerAdviceクラスには、コントローラーメソッドからの例外を処理する @ExceptionHandler メソッドを含めることができます。次の例には、このようなハンドラーメソッドが含まれています。

Java
@Controller
public class SimpleController {

    // ...

    @ExceptionHandler (1)
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}
1 @ExceptionHandlerの宣言。
Kotlin
@Controller
class SimpleController {

    // ...

    @ExceptionHandler (1)
    fun handle(ex: IOException): ResponseEntity<String> {
        // ...
    }
}
1 @ExceptionHandlerの宣言。

例外は、伝播されるトップレベルの例外(つまり、直接 IOException がスローされる)、またはトップレベルのラッパー例外内の直接の原因(たとえば、IllegalStateException内にラップされた IOException )に一致します。

例外のタイプを一致させるには、前の例に示すように、ターゲットの例外をメソッド引数として宣言することをお勧めします。または、アノテーション宣言により、一致する例外タイプを絞り込むことができます。通常、引数のシグネチャーをできるだけ具体的に指定し、対応する順序で優先順位付けされた @ControllerAdvice でプライマリルート例外マッピングを宣言することをお勧めします。詳細については、MVCセクションを参照してください。

WebFluxの @ExceptionHandler メソッドは、@RequestMapping メソッドと同じメソッド引数と戻り値をサポートしていますが、リクエスト本体および @ModelAttribute 関連のメソッド引数は例外です。

Springでの @ExceptionHandler メソッドのサポートWebFluxは、@RequestMapping メソッドの HandlerAdapter によって提供されます。詳細については、 DispatcherHandler を参照してください。

REST APIの例外

RESTサービスの一般的な要件は、応答の本文にエラーの詳細を含めることです。Spring Frameworkは、応答本文のエラー詳細の表現がアプリケーション固有であるため、自動的にそうしません。ただし、@RestController は、ResponseEntity 戻り値を持つ @ExceptionHandler メソッドを使用して、応答のステータスと本文を設定できます。そのようなメソッドを @ControllerAdvice クラスで宣言して、グローバルに適用することもできます。

Spring WebFluxにはSpring MVC ResponseEntityExceptionHandlerに相当するものがないことに注意してください。これは、WebFluxが ResponseStatusException (またはそのサブクラス)のみを生成し、HTTPステータスコードに変換する必要がないためです。

1.4.7. コントローラーのアドバイス

通常、@ExceptionHandler, @InitBinderおよび @ModelAttribute メソッドは、それらが宣言されている @Controller クラス(またはクラス階層)内で適用されます。そのようなメソッドを(コントローラー間で)よりグローバルに適用したい場合は、@ControllerAdvice または @RestControllerAdviceアノテーションが付けられたクラスで宣言できます。

@ControllerAdvice には @Componentのアノテーションが付いています。これは、そのようなクラスをコンポーネントスキャンによってSpring Beanとして登録できることを意味します。 @RestControllerAdvice は、@ControllerAdvice@ResponseBodyの両方でアノテーションが付けられた合成アノテーションです。これは、本質的に、@ExceptionHandler メソッドがメッセージ変換(ビューの解像度またはテンプレートのレンダリングに対して)によって応答本文にレンダリングされることを意味します。

起動時に、@RequestMapping および @ExceptionHandler メソッドのインフラストラクチャクラスは、@ControllerAdvice アノテーションが付けられたSpring Beanを検出し、実行時にそれらのメソッドを適用します。グローバル @ExceptionHandler メソッド( @ControllerAdviceから)はローカルメソッド( @Controllerから)の後に適用さます。対照的に、グローバル @ModelAttribute および @InitBinder メソッドは、ローカルメソッドの前に適用されます。

デフォルトでは、@ControllerAdvice メソッドはすべてのリクエスト(つまり、すべてのコントローラー)に適用されますが、次の例に示すように、アノテーションの属性を使用してコントローラーのサブセットに絞り込むことができます。

Java
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
Kotlin
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = [RestController::class])
public class ExampleAdvice1 {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class])
public class ExampleAdvice3 {}

前の例のセレクターは実行時に評価され、広範囲に使用するとパフォーマンスに悪影響を与える可能性があります。詳細については、 @ControllerAdvice (Javadoc) javadocを参照してください。

1.5. 機能エンドポイント

Spring WebFluxには、WebFlux.fnが含まれています。WebFlux.fnは、機能を使用して要求のルーティングと処理を行い、契約は不変性のために設計されています。これは、アノテーションベースのプログラミングモデルに代わるものですが、それ以外は同じリアクティブコア基盤で実行されます。

1.5.1. 概要

In WebFlux.fn, an HTTP request is handled with a HandlerFunction : a function that takes ServerRequest and returns a delayed ServerResponse (i.e. Mono<ServerResponse> ). Both the request as the response object have immutable contracts that offer JDK 8-friendly access to the HTTP request and response. HandlerFunction is the equivalent of the body of a @RequestMapping method in the annotation-based programming model.

Incoming requests are routed to a handler function with a RouterFunction : a function that takes ServerRequest and returns a delayed HandlerFunction (i.e. Mono<HandlerFunction> ). When the router function matches, a handler function is returned; otherwise an empty Mono. RouterFunction is the equivalent of a @RequestMapping annotation, but with the major difference that router functions provide not just data, but also behavior.

RouterFunctions.route() は、次の例に示すように、ルーターの作成を容易にするルータービルダーを提供します。

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
    .POST("/person", handler::createPerson)
    .build();


public class PersonHandler {

    // ...

    public Mono<ServerResponse> listPeople(ServerRequest request) {
        // ...
    }

    public Mono<ServerResponse> createPerson(ServerRequest request) {
        // ...
    }

    public Mono<ServerResponse> getPerson(ServerRequest request) {
        // ...
    }
}
Kotlin
val repository: PersonRepository = ...
val handler = PersonHandler(repository)

val route = coRouter { (1)
    accept(APPLICATION_JSON).nest {
        GET("/person/{id}", handler::getPerson)
        GET("/person", handler::listPeople)
    }
    POST("/person", handler::createPerson)
}


class PersonHandler(private val repository: PersonRepository) {

    // ...

    suspend fun listPeople(request: ServerRequest): ServerResponse {
        // ...
    }

    suspend fun createPerson(request: ServerRequest): ServerResponse {
        // ...
    }

    suspend fun getPerson(request: ServerRequest): ServerResponse {
        // ...
    }
}
1CoroutinesルーターDSLを使用してルーターを作成します。Reactiveの代替手段も router { }を介して使用できます。

RouterFunction を実行する1つの方法は、RouterFunctionHttpHandler に変換し、組み込みサーバーアダプターの 1つを介してインストールすることです。

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

ほとんどのアプリケーションは、WebFlux Java構成を介して実行できます。サーバーの実行を参照してください。

1.5.2. HandlerFunction

ServerRequest および ServerResponse は、HTTPリクエストおよび応答へのJDK 8フレンドリーなアクセスを提供する不変のインターフェースです。要求と応答の両方が、ボディストリームに対するリアクティブストリーム(英語) バックプレッシャーを提供します。要求本文は、Reactor Flux または Monoで表されます。応答本体は、Flux および Monoを含む、Reactive Streams Publisherで表されます。詳細については、リアクティブライブラリを参照してください。

ServerRequest

ServerRequest はHTTPメソッド、URI、ヘッダー、およびクエリパラメーターへのアクセスを提供し、ボディへのアクセスは body メソッドを介して提供されます。

次の例では、リクエストの本文を Mono<String>に抽出します。

Java
Mono<String> string = request.bodyToMono(String.class);
Kotlin
val string = request.awaitBody<String>()

次の例では、Flux<Person> (またはKotlinの Flow<Person> )にボディを抽出します。Person オブジェクトは、JSONやXMLなどの直列化された形式からデコードされます。

Java
Flux<Person> people = request.bodyToFlux(Person.class);
Kotlin
val people = request.bodyToFlow<Person>()

上記の例は、より一般的な ServerRequest.body(BodyExtractor)を使用するショートカットであり、BodyExtractor 機能戦略インターフェースを受け入れます。ユーティリティクラス BodyExtractors は、多数のインスタンスへのアクセスを提供します。例:上記の例は、次のように書くこともできます。

Java
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
Kotlin
    val string = request.body(BodyExtractors.toMono(String::class.java)).awaitFirst()
    val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow()

次の例は、フォームデータにアクセスする方法を示しています。

Java
Mono<MultiValueMap<String, String> map = request.formData();
Kotlin
val map = request.awaitFormData()

次の例は、マップとしてマルチパートデータにアクセスする方法を示しています。

Java
Mono<MultiValueMap<String, Part> map = request.multipartData();
Kotlin
val map = request.awaitMultipartData()

次の例は、ストリーミング方式で一度に1つずつマルチパートにアクセスする方法を示しています。

Java
Flux<Part> parts = request.body(BodyExtractors.toParts());
Kotlin
val parts = request.body(BodyExtractors.toParts()).asFlow()
ServerResponse

ServerResponse はHTTPレスポンスへのアクセスを提供します。これは不変なので、build メソッドを使用してHTTPレスポンスを作成できます。ビルダーを使用して、応答ステータスを設定したり、応答ヘッダーを追加したり、本文を提供したりできます。次の例では、JSONコンテンツで200(OK)応答を作成します。

Java
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
Kotlin
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)

次の例は、Location ヘッダーを持ち、本文がない201(CREATED)応答を作成する方法を示しています。

Java
URI location = ...
ServerResponse.created(location).build();
Kotlin
val location: URI = ...
ServerResponse.created(location).build()

使用されるコーデックに応じて、ヒントパラメータを渡して、本体の直列化または逆直列化の方法をカスタマイズできます。例:Jackson JSONビュー(英語) を指定するには:

Java
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
Kotlin
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
ハンドラークラス

次の例に示すように、ハンドラー関数をラムダとして記述できます。

Java
HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().bodyValue("Hello World");
Kotlin
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }

これは便利ですが、アプリケーションでは複数の関数が必要であり、複数のインラインラムダが乱雑になる可能性があります。関連するハンドラー関数を、アノテーションベースのアプリケーションの @Controller と同様のロールを持つハンドラークラスにグループ化すると便利です。例:次のクラスは、リアクティブ Person リポジトリを公開します。

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

public class PersonHandler {

    private final PersonRepository repository;

    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }

    public Mono<ServerResponse> listPeople(ServerRequest request) { (1)
        Flux<Person> people = repository.allPeople();
        return ok().contentType(APPLICATION_JSON).body(people, Person.class);
    }

    public Mono<ServerResponse> createPerson(ServerRequest request) { (2)
        Mono<Person> person = request.bodyToMono(Person.class);
        return ok().build(repository.savePerson(person));
    }

    public Mono<ServerResponse> getPerson(ServerRequest request) { (3)
        int personId = Integer.valueOf(request.pathVariable("id"));
        return repository.getPerson(personId)
            .flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
}
1 listPeople は、リポジトリで見つかったすべての Person オブジェクトをJSONとして返すハンドラー関数です。
2 createPerson は、リクエスト本文に含まれる新しい Person を保存するハンドラー関数です。 PersonRepository.savePerson(Person)Mono<Void>を返すことに注意してください。空の Mono は、要求から人が読み取られて保管されたときに完了シグナルを発行します。そのため、build(Publisher<Void>) メソッドを使用して、完了信号を受信したとき(つまり、Person が保存されたとき)に応答を送信します。
3 getPerson は、id パス変数で識別される1人の人物を返すハンドラー関数です。 Person をリポジトリから取得し、見つかった場合はJSONレスポンスを作成します。見つからない場合は、switchIfEmpty(Mono<T>) を使用して404 未検出応答を返します。
Kotlin
class PersonHandler(private val repository: PersonRepository) {

    suspend fun listPeople(request: ServerRequest): ServerResponse { (1)
        val people: Flow<Person> = repository.allPeople()
        return ok().contentType(APPLICATION_JSON).bodyAndAwait(people);
    }

    suspend fun createPerson(request: ServerRequest): ServerResponse { (2)
        val person = request.awaitBody<Person>()
        repository.savePerson(person)
        return ok().buildAndAwait()
    }

    suspend fun getPerson(request: ServerRequest): ServerResponse { (3)
        val personId = request.pathVariable("id").toInt()
        return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).bodyValueAndAwait(it) }
                ?: ServerResponse.notFound().buildAndAwait()

    }
}
1 listPeople は、リポジトリで見つかったすべての Person オブジェクトをJSONとして返すハンドラー関数です。
2 createPerson は、リクエスト本文に含まれる新しい Person を保存するハンドラー関数です。 PersonRepository.savePerson(Person) は、戻り値のない一時停止関数であることに注意してください。
3 getPerson は、id パス変数で識別される1人の人物を返すハンドラー関数です。 Person をリポジトリから取得し、見つかった場合はJSONレスポンスを作成します。見つからない場合、404 未検出レスポンスを返します。
検証

機能エンドポイントは、Springの検証機能を使用して、要求本文に検証を適用できます。例: PersonのカスタムSpring バリデーター実装が指定された場合:

Java
public class PersonHandler {

    private final Validator validator = new PersonValidator(); (1)

    // ...

    public Mono<ServerResponse> createPerson(ServerRequest request) {
        Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); (2)
        return ok().build(repository.savePerson(person));
    }

    private void validate(Person person) {
        Errors errors = new BeanPropertyBindingResult(person, "person");
        validator.validate(person, errors);
        if (errors.hasErrors()) {
            throw new ServerWebInputException(errors.toString()); (3)
        }
    }
}
1 Validator インスタンスを作成します。
2検証を適用します。
3400応答に対して例外を発生させます。
Kotlin
class PersonHandler(private val repository: PersonRepository) {

    private val validator = PersonValidator() (1)

    // ...

    suspend fun createPerson(request: ServerRequest): ServerResponse {
        val person = request.awaitBody<Person>()
        validate(person) (2)
        repository.savePerson(person)
        return ok().buildAndAwait()
    }

    private fun validate(person: Person) {
        val errors: Errors = BeanPropertyBindingResult(person, "person");
        validator.validate(person, errors);
        if (errors.hasErrors()) {
            throw ServerWebInputException(errors.toString()) (3)
        }
    }
}
1 Validator インスタンスを作成します。
2検証を適用します。
3400応答に対して例外を発生させます。

ハンドラーは、LocalValidatorFactoryBeanに基づいてグローバル Validator インスタンスを作成および注入することにより、標準Bean検証API(JSR-303)を使用することもできます。Spring検証を参照してください。

1.5.3. RouterFunction

ルーター関数を使用して、要求を対応する HandlerFunctionにルーティングします。通常、ルーター関数は自分で作成するのではなく、RouterFunctions ユーティリティクラスのメソッドを使用して作成します。 RouterFunctions.route() (パラメーターなし)は、ルーター関数を作成するための流なビルダーを提供しますが、RouterFunctions.route(RequestPredicate, HandlerFunction) はルーターを直接作成する方法を提供します。

通常、route() ビルダーを使用することをお勧めします。これは、検出が困難な静的インポートを必要とせずに、一般的なマッピングシナリオに便利なショートカットを提供するためです。たとえば、ルーター関数ビルダーは、GET要求のマッピングを作成するメソッド GET(String, HandlerFunction) を提供します。POST用の POST(String, HandlerFunction)

HTTPメソッドベースのマッピングに加えて、ルートビルダーは、要求にマッピングするときに追加の述語を導入する方法を提供します。HTTPメソッドごとに、RequestPredicate をパラメーターとして受け取るオーバーロードされたバリアントがありますが、追加の制約を表現できます。

述部

独自の RequestPredicateを作成できますが、RequestPredicates ユーティリティクラスは、リクエストパス、HTTPメソッド、コンテンツタイプなどに基づいて、一般的に使用される実装を提供します。次の例では、要求述語を使用して、Accept ヘッダーに基づいて制約を作成します。

Java
RouterFunction<ServerResponse> route = RouterFunctions.route()
    .GET("/hello-world", accept(MediaType.TEXT_PLAIN),
        request -> ServerResponse.ok().bodyValue("Hello World")).build();
Kotlin
val route = coRouter {
    GET("/hello-world", accept(TEXT_PLAIN)) {
        ServerResponse.ok().bodyValueAndAwait("Hello World")
    }
}

以下を使用して、複数の要求述語を一緒に作成できます。

  • RequestPredicate.and(RequestPredicate)  — both must match.

  • RequestPredicate.or(RequestPredicate)  — either can match.

RequestPredicates の述部の多くが構成されています。例: RequestPredicates.GET(String)RequestPredicates.method(HttpMethod)RequestPredicates.path(String)から構成されています。上記の例では、ビルダーが RequestPredicates.GET を内部で使用し、それを accept 述語で構成しているため、2つの要求述語も使用しています。

ルート

Router functions are evaluated in order: if the first route does not match, the second is evaluated, and so on. Therefore, it makes sense to declare more specific routes before general ones. Note that this behavior is different from the annotation-based programming model, where the "most specific" controller method is picked automatically.

ルーター関数ビルダーを使用する場合、定義されたすべてのルートは、build()から返される1つの RouterFunction に構成されます。複数のルーター関数を一緒に構成する他の方法もあります。

  • RouterFunctions.route() ビルダーのadd(RouterFunction)

  • RouterFunction.and(RouterFunction)

  • RouterFunction.andRoute(RequestPredicate, HandlerFunction)  — shortcut for RouterFunction.and() with nested RouterFunctions.route() .

次の例は、4つのルートの構成を示しています。

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> otherRoute = ...

RouterFunction<ServerResponse> route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2)
    .POST("/person", handler::createPerson) (3)
    .add(otherRoute) (4)
    .build();
1 JSONに一致する Accept ヘッダーを持つGET /person/{id}PersonHandler.getPersonにルーティングされます
2 JSONに一致する Accept ヘッダーを持つGET /personPersonHandler.listPeopleにルーティングされます
3 追加の述部のないPOST /personPersonHandler.createPersonにマップされます
4 otherRoute は、他の場所で作成され、構築されたルートに追加されるルーター関数です。
Kotlin
import org.springframework.http.MediaType.APPLICATION_JSON

val repository: PersonRepository = ...
val handler = PersonHandler(repository);

val otherRoute: RouterFunction<ServerResponse> = coRouter {  }

val route = coRouter {
    GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1)
    GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2)
    POST("/person", handler::createPerson) (3)
}.and(otherRoute) (4)
1 JSONに一致する Accept ヘッダーを持つGET /person/{id}PersonHandler.getPersonにルーティングされます
2 JSONに一致する Accept ヘッダーを持つGET /personPersonHandler.listPeopleにルーティングされます
3 追加の述部のないPOST /personPersonHandler.createPersonにマップされます
4 otherRoute は、他の場所で作成され、構築されたルートに追加されるルーター関数です。
ネストされたルート

ルーター関数のグループが共有述語(共有パスなど)を持つことは一般的です。上記の例では、共有述語は、3つのルートで使用される /personに一致するパス述語になります。アノテーションを使用する場合、/personにマップする型レベルの @RequestMapping アノテーションを使用して、この重複を削除します。WebFlux.fnでは、経路述部は、ルーター関数ビルダーの path メソッドを介して共有できます。たとえば、上記の例の最後の数行は、ネストされたルートを使用して次のように改善できます。

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", builder -> builder (1)
        .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        .GET("", accept(APPLICATION_JSON), handler::listPeople)
        .POST("/person", handler::createPerson))
    .build();
1 path の2番目のパラメーターは、ルータービルダーを使用するコンシューマーであることに注意してください。
Kotlin
val route = coRouter {
    "/person".nest {
        GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        GET("", accept(APPLICATION_JSON), handler::listPeople)
        POST("/person", handler::createPerson)
    }
}

パスベースのネストが最も一般的ですが、Builderで nest メソッドを使用して、あらゆる種類の述語にネストできます。上記には、共有 Accept -header述部の形式での重複がまだ含まれています。 nest メソッドと acceptを併用すると、さらに改善できます。

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople))
        .POST("/person", handler::createPerson))
    .build();
Kotlin
val route = coRouter {
    "/person".nest {
        accept(APPLICATION_JSON).nest {
            GET("/{id}", handler::getPerson)
            GET("", handler::listPeople)
            POST("/person", handler::createPerson)
        }
    }
}

1.5.4. サーバーの実行

HTTPサーバーでルーター関数をどのように実行しますか?簡単なオプションは、次のいずれかを使用して、ルーター関数を HttpHandler に変換することです。

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

その後、サーバー固有の手順についてHttpHandlerに従うことにより、返された HttpHandler を多数のサーバーアダプターで使用できます。

Spring Bootでも使用されるより一般的なオプションは、WebFlux構成を介して DispatcherHandler ベースのセットアップで実行することです。WebFlux構成は、Spring構成を使用して、要求の処理に必要なコンポーネントを宣言します。WebFlux Java構成は、機能エンドポイントをサポートするために次のインフラストラクチャコンポーネントを宣言します。

  • RouterFunctionMapping : Spring構成内の1つ以上の RouterFunction<?> Beanを検出し、RouterFunction.andOtherを介して結合し、結果の合成された RouterFunctionに要求をルーティングします。

  • HandlerFunctionAdapter : DispatcherHandler が要求にマップされた HandlerFunction を呼び出せるようにする単純なアダプター。

  • ServerResponseResultHandler : ServerResponsewriteTo メソッドを呼び出すことにより、HandlerFunction の呼び出しからの結果を処理します。

上記のコンポーネントにより、機能エンドポイントは DispatcherHandler 要求処理ライフサイクルに適合し、アノテーション付きコントローラー(存在する場合)と並行して(潜在的に)実行されます。また、Spring Boot WebFluxスターターが機能エンドポイントを有効にする方法でもあります。

次の例は、WebFlux Java構成を示しています(実行方法については、DispatcherHandlerを参照してください)。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Bean
    public RouterFunction<?> routerFunctionA() {
        // ...
    }

    @Bean
    public RouterFunction<?> routerFunctionB() {
        // ...
    }

    // ...

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        // configure message conversion...
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // configure CORS...
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // configure view resolution for HTML rendering...
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    @Bean
    fun routerFunctionA(): RouterFunction<*> {
        // ...
    }

    @Bean
    fun routerFunctionB(): RouterFunction<*> {
        // ...
    }

    // ...

    override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
        // configure message conversion...
    }

    override fun addCorsMappings(registry: CorsRegistry) {
        // configure CORS...
    }

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        // configure view resolution for HTML rendering...
    }
}

1.5.5. ハンドラー関数のフィルタリング

ルーティング関数ビルダーで before, afterまたは filter メソッドを使用して、ハンドラー関数をフィルター処理できます。アノテーションを使用すると、@ControllerAdviceServletFilter、またはその両方を使用して同様の機能を実現できます。フィルターは、ビルダーによって構築されたすべてのルートに適用されます。これは、ネストされたルートで定義されたフィルターが「トップレベル」ルートに適用されないことを意味します。たとえば、次の例を考えてみましょう。

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople)
            .before(request -> ServerRequest.from(request) (1)
                .header("X-RequestHeader", "Value")
                .build()))
        .POST("/person", handler::createPerson))
    .after((request, response) -> logResponse(response)) (2)
    .build();
1カスタムリクエストヘッダーを追加する before フィルターは、2つのGETルートにのみ適用されます。
2応答を記録する after フィルターは、ネストされたものを含むすべてのルートに適用されます。
Kotlin
val route = router {
    "/person".nest {
        GET("/{id}", handler::getPerson)
        GET("", handler::listPeople)
        before { (1)
            ServerRequest.from(it)
                    .header("X-RequestHeader", "Value").build()
        }
        POST("/person", handler::createPerson)
        after { _, response -> (2)
            logResponse(response)
        }
    }
}
1カスタムリクエストヘッダーを追加する before フィルターは、2つのGETルートにのみ適用されます。
2応答を記録する after フィルターは、ネストされたものを含むすべてのルートに適用されます。

The filter method on the router builder takes a HandlerFilterFunction : a function that takes a ServerRequest and HandlerFunction and returns a ServerResponse . The handler function parameter represents the next element in the chain. This is typically the handler that is routed to, but it can also be another filter if multiple are applied.

特定のパスが許可されているかどうかを判断できる SecurityManager があると仮定して、ルートに簡単なセキュリティフィルターを追加できます。次の例は、その方法を示しています。

Java
SecurityManager securityManager = ...

RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople))
        .POST("/person", handler::createPerson))
    .filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
    })
    .build();
Kotlin
val securityManager: SecurityManager = ...

val route = router {
        ("/person" and accept(APPLICATION_JSON)).nest {
            GET("/{id}", handler::getPerson)
            GET("", handler::listPeople)
            POST("/person", handler::createPerson)
            filter { request, next ->
                if (securityManager.allowAccessTo(request.path())) {
                    next(request)
                }
                else {
                    status(UNAUTHORIZED).build();
                }
            }
        }
    }

上記の例は、next.handle(ServerRequest) の呼び出しがオプションであることを示しています。アクセスが許可されている場合、ハンドラー関数のみを実行できます。

ルーター関数ビルダーで filter メソッドを使用する以外に、RouterFunction.filter(HandlerFilterFunction)を介して既存のルーター関数にフィルターを適用することができます。

機能的なエンドポイントのCORSサポートは、専用の CorsWebFilter を介して提供されます。

1.6. URIリンク

このセクションでは、URIを準備するためにSpring Frameworkで使用可能なさまざまなオプションについて説明します。

1.6.1. UriComponents

Spring MVCおよびSpring WebFlux

UriComponentsBuilder は、次の例に示すように、変数を持つURIテンプレートからURIを作成できます。

Java
UriComponents uriComponents = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")  (1)
        .queryParam("q", "{q}")  (2)
        .encode() (3)
        .build(); (4)

URI uri = uriComponents.expand("Westin", "123").toUri();  (5)
1URIテンプレートを使用した静的ファクトリメソッド。
2URIコンポーネントを追加または置換します。
3URIテンプレートとURI変数をエンコードするよう要求します。
4 UriComponentsをビルドします。
5変数を展開し、URIを取得します。
Kotlin
val uriComponents = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")  (1)
        .queryParam("q", "{q}")  (2)
        .encode() (3)
        .build() (4)

val uri = uriComponents.expand("Westin", "123").toUri()  (5)
1URIテンプレートを使用した静的ファクトリメソッド。
2URIコンポーネントを追加または置換します。
3URIテンプレートとURI変数をエンコードするよう要求します。
4 UriComponentsをビルドします。
5変数を展開し、URIを取得します。

前述の例は、次の例に示すように、1つのチェーンに統合し、buildAndExpandで短縮できます。

Java
URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("Westin", "123")
        .toUri();
Kotlin
val uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("Westin", "123")
        .toUri()

次の例に示すように、URIに直接移動することで(エンコードを暗示する)、さらに短くすることができます。

Java
URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123");
Kotlin
val uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123")

次の例に示すように、完全なURIテンプレートを使用してさらに短くします。

Java
URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}?q={q}")
        .build("Westin", "123");
Kotlin
val uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}?q={q}")
        .build("Westin", "123")

1.6.2. UriBuilder

Spring MVCおよびSpring WebFlux

UriComponentsBuilder UriBuilderを実装しています。 UriBuilderFactoryを使用して、UriBuilderを作成できます。 UriBuilderFactoryUriBuilder は、ベースURL、エンコード設定、その他の詳細などの共有構成に基づいて、URIテンプレートからURIを構築するプラグ可能なメカニズムを提供します。

RestTemplate および WebClientUriBuilderFactory で構成して、URIの準備をカスタマイズできます。 DefaultUriBuilderFactory は、UriComponentsBuilder を内部で使用し、共有構成オプションを公開する UriBuilderFactory のデフォルト実装です。

次の例は、RestTemplateを構成する方法を示しています。

Java
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
Kotlin
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode

val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES

val restTemplate = RestTemplate()
restTemplate.uriTemplateHandler = factory

次の例では、WebClientを構成します。

Java
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
Kotlin
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode

val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES

val client = WebClient.builder().uriBuilderFactory(factory).build()

さらに、DefaultUriBuilderFactory を直接使用することもできます。 UriComponentsBuilder の使用に似ていますが、次の例に示すように、静的ファクトリーメソッドの代わりに、構成と設定を保持する実際のインスタンスです。

Java
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);

URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123");
Kotlin
val baseUrl = "https://example.com"
val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl)

val uri = uriBuilderFactory.uriString("/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123")

1.6.3. URIエンコーディング

Spring MVCおよびSpring WebFlux

UriComponentsBuilder は、2つのレベルでエンコードオプションを公開します。

どちらのオプションも、非ASCII文字と不正な文字をエスケープされたオクテットに置き換えます。ただし、最初のオプションは、URI変数に表示される予約された意味で文字を置き換えます。

「;」を検討してください。これはパスでは有効ですが、意味は予約されています。最初のオプションは「;」を置き換えます。URI変数には「%3B」が含まれますが、URIテンプレートには含まれません。対照的に、2番目のオプションはパス内の正当な文字であるため、「;」を置き換えることはありません。

ほとんどの場合、最初のオプションはURI変数を完全にエンコードされる不透明なデータとして扱うため、期待される結果が得られる可能性がありますが、オプション2はURI変数に意図的に予約文字が含まれている場合にのみ役立ちます。

次の例では、最初のオプションを使用しています。

Java
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("New York", "foo+bar")
        .toUri();

// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
Kotlin
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("New York", "foo+bar")
        .toUri()

// Result is "/hotel%20list/New%20York?q=foo%2Bbar"

次の例に示すように、URIに直接移動することで、前述の例を短縮できます(エンコードを意味します)。

Java
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .build("New York", "foo+bar")
Kotlin
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .build("New York", "foo+bar")

次の例に示すように、完全なURIテンプレートを使用してさらに短縮できます。

Java
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
        .build("New York", "foo+bar")
Kotlin
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
        .build("New York", "foo+bar")

WebClient および RestTemplate は、UriBuilderFactory 戦略により内部的にURIテンプレートを展開およびエンコードします。どちらもカスタム戦略で構成できます。次の例に示すように:

Java
String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
Kotlin
val baseUrl = "https://example.com"
val factory = DefaultUriBuilderFactory(baseUrl).apply {
    encodingMode = EncodingMode.TEMPLATE_AND_VALUES
}

// Customize the RestTemplate..
val restTemplate = RestTemplate().apply {
    uriTemplateHandler = factory
}

// Customize the WebClient..
val client = WebClient.builder().uriBuilderFactory(factory).build()

DefaultUriBuilderFactory 実装は、UriComponentsBuilder を内部的に使用してURIテンプレートを展開およびエンコードします。ファクトリーとして、以下のエンコードモードのいずれかに基づいて、エンコードへのアプローチを構成する単一の場所を提供します。

  • TEMPLATE_AND_VALUES : 前のリストの最初のオプションに対応する UriComponentsBuilder#encode()を使用して、URIテンプレートを事前エンコードし、展開時にURI変数を厳密にエンコードします。

  • VALUES_ONLY : URIテンプレートをエンコードせず、代わりに、UriUtils#encodeUriUriVariables を使用してURI変数をテンプレートに展開する前にURI変数に厳密なエンコードを適用します。

  • URI_COMPONENTS : 前のリストの2番目のオプションに対応する UriComponents#encode()を使用して、URI変数が展開された後に URIコンポーネント値をエンコードします。

  • NONE : エンコードは適用されません。

RestTemplate は、歴史的な理由と後方互換性のために EncodingMode.URI_COMPONENTS に設定されています。 WebClient は、DefaultUriBuilderFactoryのデフォルト値に依存しています。これは、5.0.xの EncodingMode.URI_COMPONENTS から5.1の EncodingMode.TEMPLATE_AND_VALUES に変更されました。

1.7. CORS

Spring WebFluxを使用すると、CORS(クロスオリジンリソース共有)を処理できます。このセクションでは、その方法について説明します。

1.7.1. 導入

セキュリティ上の理由から、ブラウザは現在のオリジンの外部のリソースへのAJAX呼び出しを禁止しています。例:銀行口座を1つのタブに、evil.comを別のタブに持つことができます。evil.comのスクリプトは、アカウントからお金を引き出すなど、認証情報を使用して銀行のAPIにAJAXリクエストを行うことはできません!

Cross-Origin Resource Sharing(CORS)は、ほとんどのブラウザ(英語) で実装されているW3C仕様(英語) であり、IFRAMEまたはJSONPに基づく安全性が低く、強力ではない回避策を使用するのではなく、認可される種類のクロスドメインリクエストを指定できます。

1.7.2. 処理

CORS仕様は、プリフライト、シンプル、および実際のリクエストを区別します。CORSがどのように機能するかを学ぶために、他の多くの人と一緒にこの記事(英語) を読むか、詳細について仕様を参照してください。

Spring WebFlux HandlerMapping 実装は、CORSの組み込みサポートを提供します。リクエストをハンドラーに正常にマッピングした後、HandlerMapping は指定されたリクエストとハンドラーのCORS設定をチェックし、さらにアクションを実行します。プリフライトリクエストは直接処理されますが、シンプルで実際のCORSリクエストはインターセプトされ、検証され、必要なCORSレスポンスヘッダーが設定されます。

クロスオリジンリクエストを有効にするには(つまり、Origin ヘッダーが存在し、リクエストのホストとは異なる)、明示的に宣言されたCORS構成が必要です。一致するCORS設定が見つからない場合、プリフライトリクエストは拒否されます。CORSヘッダーは単純および実際のCORS要求の応答に追加されないため、ブラウザーはそれらを拒否します。

HandlerMapping は、URLパターンベースの CorsConfiguration マッピングで個別に構成(Javadoc) できます。ほとんどの場合、アプリケーションはWebFlux Java構成を使用してこのようなマッピングを宣言します。これにより、単一のグローバルマップがすべての HadlerMappping 実装に渡されます。

HandlerMapping レベルのグローバルCORS構成と、よりきめ細かいハンドラーレベルのCORS構成を組み合わせることができます。例:アノテーション付きコントローラーは、クラスまたはメソッドレベルの @CrossOrigin アノテーションを使用できます(他のハンドラーは CorsConfigurationSourceを実装できます)。

The rules for combining global and local configuration are generally additive — for example, all global and all local origins. For those attributes where only a single value can be accepted, such as allowCredentials and maxAge , the local overrides the global value. See CorsConfiguration#combine(CorsConfiguration) (Javadoc) for more details.

ソースから詳細を確認するか、高度なカスタマイズを行うには、次を参照してください。

  • CorsConfiguration

  • CorsProcessor および DefaultCorsProcessor

  • AbstractHandlerMapping

1.7.3. @CrossOrigin

@CrossOrigin (Javadoc) アノテーションは、次の例に示すように、アノテーション付きコントローラーメソッドでクロスオリジンリクエストを有効にします。

Java
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@RestController
@RequestMapping("/account")
class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    suspend fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    suspend fun remove(@PathVariable id: Long) {
        // ...
    }
}

デフォルトでは、@CrossOrigin は次を許可します。

  • すべての起源。

  • すべてのヘッダー。

  • コントローラーメソッドがマップされるすべてのHTTPメソッド。

allowedCredentials は、ユーザー固有の機密情報(CookieやCSRFトークンなど)を公開する信頼レベルを確立し、適切な場合にのみ使用する必要があるため、デフォルトでは有効になっていません。

maxAge は30分に設定されています。

@CrossOrigin はクラスレベルでもサポートされ、すべてのメソッドに継承されます。次の例では、特定のドメインを指定し、maxAge を1時間に設定します。

Java
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@CrossOrigin("https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {

    @GetMapping("/{id}")
    suspend fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    suspend fun remove(@PathVariable id: Long) {
        // ...
    }
}

次の例に示すように、@CrossOrigin はクラスレベルとメソッドレベルの両方で使用できます。

Java
@CrossOrigin(maxAge = 3600) (1)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin("https://domain2.com") (2)
    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}
1クラスレベルで @CrossOrigin を使用します。
2メソッドレベルで @CrossOrigin を使用します。
Kotlin
@CrossOrigin(maxAge = 3600) (1)
@RestController
@RequestMapping("/account")
class AccountController {

    @CrossOrigin("https://domain2.com") (2)
    @GetMapping("/{id}")
    suspend fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    suspend fun remove(@PathVariable id: Long) {
        // ...
    }
}
1クラスレベルで @CrossOrigin を使用します。
2メソッドレベルで @CrossOrigin を使用します。

1.7.4. グローバル構成

細分化されたコントローラーメソッドレベルの構成に加えて、おそらくグローバルなCORS構成も定義する必要があります。URLベースの CorsConfiguration マッピングは、HandlerMappingで個別に設定できます。ただし、ほとんどのアプリケーションは、WebFlux Java構成を使用してこれを実行します。

デフォルトでは、グローバル構成により以下が有効になります。

  • すべての起源。

  • すべてのヘッダー。

  • GET, HEADおよび POST メソッド。

allowedCredentials はデフォルトでは有効ではありません。これは、ユーザー固有の機密情報(CookieやCSRFトークンなど)を公開する信頼レベルを確立し、適切な場合にのみ使用する必要があるためです。

maxAge は30分に設定されています。

WebFlux Java構成でCORSを有効にするには、次の例に示すように、CorsRegistry コールバックを使用できます。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/api/**")
            .allowedOrigins("https://domain2.com")
            .allowedMethods("PUT", "DELETE")
            .allowedHeaders("header1", "header2", "header3")
            .exposedHeaders("header1", "header2")
            .allowCredentials(true).maxAge(3600);

        // Add more mappings...
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun addCorsMappings(registry: CorsRegistry) {

        registry.addMapping("/api/**")
                .allowedOrigins("https://domain2.com")
                .allowedMethods("PUT", "DELETE")
                .allowedHeaders("header1", "header2", "header3")
                .exposedHeaders("header1", "header2")
                .allowCredentials(true).maxAge(3600)

        // Add more mappings...
    }
}

1.7.5. CORS WebFilter

組み込みの CorsWebFilter (Javadoc) を使用してCORSサポートを適用できます。これは、機能的なエンドポイントに適しています

CorsFilter をSpring Securityで使用しようとする場合、Spring SecurityにはCORSのサポートが組み込まれていることに注意してください。

フィルターを構成するには、次の例に示すように、CorsWebFilter Beanを宣言し、CorsConfigurationSource をコンストラクターに渡すことができます。

Java
@Bean
CorsWebFilter corsFilter() {

    CorsConfiguration config = new CorsConfiguration();

    // Possibly...
    // config.applyPermitDefaultValues()

    config.setAllowCredentials(true);
    config.addAllowedOrigin("https://domain1.com");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);

    return new CorsWebFilter(source);
}
Kotlin
@Bean
fun corsFilter(): CorsWebFilter {

    val config = CorsConfiguration()

    // Possibly...
    // config.applyPermitDefaultValues()

    config.allowCredentials = true
    config.addAllowedOrigin("https://domain1.com")
    config.addAllowedHeader("*")
    config.addAllowedMethod("*")

    val source = UrlBasedCorsConfigurationSource().apply {
        registerCorsConfiguration("/**", config)
    }
    return CorsWebFilter(source)
}

1.8. Webセキュリティ

Spring Securityプロジェクトは、悪意のある悪用からWebアプリケーションを保護するためのサポートを提供します。以下を含むSpring Securityリファレンスドキュメントを参照してください。

1.9. ビューテクノロジー

Spring WebFluxでのビュー技術の使用はプラグ可能です。Thymeleaf、FreeMarker、またはその他の表示技術を使用するかどうかは、主に構成の変更の問題です。この章では、Spring WebFluxと統合されたビューテクノロジーについて説明します。すでにビューリゾルバーに精通していることを前提としています。

1.9.1. Thymeleaf

Thymeleafは、ダブルクリックでブラウザーでプレビューできる自然なHTMLテンプレートを強調する最新のサーバー側Javaテンプレートエンジンです。これは、UIテンプレート(デザイナーなど)での独立した作業に必要なく、非常に役立ちます。実行中のサーバー。Thymeleafは広範な機能セットを提供し、積極的に開発および保守されています。より完全な紹介については、Thymeleaf(英語) プロジェクトのホームページを参照してください。

ThymeleafとSpring WebFluxの統合は、Thymeleafプロジェクトによって管理されています。構成には、SpringResourceTemplateResolver, SpringWebFluxTemplateEngineThymeleafReactiveViewResolverなどのいくつかのBean宣言が含まれます。詳細については、Thymeleaf+Spring(英語) およびWebFlux統合発表(英語) を参照してください。

1.9.2. FreeMarker

Apache FreeMarker(英語) は、HTMLからメールなどへのあらゆる種類のテキスト出力を生成するためのテンプレートエンジンです。Spring Frameworkには、Spring WebFluxとFreeMarkerテンプレートを使用するための組み込みの統合機能があります。

構成を表示

次の例は、FreeMarkerをビューテクノロジーとして設定する方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.freeMarker();
    }

    // Configure FreeMarker...

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("classpath:/templates/freemarker");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.freeMarker()
    }

    // Configure FreeMarker...

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("classpath:/templates/freemarker")
    }
}

テンプレートは、前の例に示されている FreeMarkerConfigurerで指定されたディレクトリに保存する必要があります。上記の構成で、コントローラーがビュー名 welcomeを返す場合、リゾルバーは classpath:/templates/freemarker/welcome.ftl テンプレートを探します。

FreeMarkerの設定

FreeMarkerConfigurer Beanで適切なBeanプロパティを設定することにより、FreeMarkerの「設定」と「SharedVariables」をFreeMarker Configuration オブジェクト(Springによって管理される)に直接渡すことができます。 freemarkerSettings プロパティには java.util.Properties オブジェクトが必要で、freemarkerVariables プロパティには java.util.Mapが必要です。次の例は、FreeMarkerConfigurerの使用方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    // ...

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        Map<String, Object> variables = new HashMap<>();
        variables.put("xml_escape", new XmlEscape());

        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("classpath:/templates");
        configurer.setFreemarkerVariables(variables);
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    // ...

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("classpath:/templates")
        setFreemarkerVariables(mapOf("xml_escape" to XmlEscape()))
    }
}

Configuration オブジェクトに適用される設定と変数の詳細については、FreeMarkerのドキュメントを参照してください。

フォーム処理

Springは、特に <spring:bind/> 要素を含むJSPで使用するタグライブラリを提供します。この要素により、フォームは主にフォームバッキングオブジェクトの値を表示し、Webまたはビジネス層の Validator からの検証の失敗の結果を表示できます。Springは、FreeMarkerと同じ機能をサポートしていますが、フォーム入力要素自体を生成するための追加の便利なマクロがあります。

バインドマクロ

マクロの標準セットは、FreeMarkerの spring-webflux.jar ファイル内で維持されるため、適切に構成されたアプリケーションで常に使用可能です。

Springテンプレートライブラリで定義されているマクロの一部は内部(プライベート)と見なされますが、マクロ定義にはそのようなスコープは存在せず、呼び出し元のコードとユーザーテンプレートにすべてのマクロが表示されます。次のセクションでは、テンプレート内から直接呼び出す必要があるマクロのみに焦点を当てます。マクロコードを直接表示する場合、ファイルは spring.ftl と呼ばれ、org.springframework.web.reactive.result.view.freemarker パッケージに含まれています。

バインディングサポートの詳細については、Spring MVCの簡単なバインドを参照してください。

フォームマクロ

FreeMarkerテンプレートに対するSpringのフォームマクロサポートの詳細については、Spring MVCドキュメントの次のセクションを参照してください。

1.9.3. スクリプトビュー

Spring Frameworkには、JSR-223(英語) Javaスクリプトエンジン上で実行できるテンプレートライブラリでSpring WebFluxを使用するための組み込みの統合があります。次の表は、さまざまなスクリプトエンジンでテストしたテンプレートライブラリを示しています。

スクリプトライブラリスクリプトエンジン

Handlebars(英語)

Nashorn(英語)

Mustache(英語)

Nashorn(英語)

React(英語)

Nashorn(英語)

EJS(英語)

Nashorn(英語)

ERB(英語)

JRuby(英語)

文字列テンプレート(英語)

Jython(英語)

Kotlinスクリプトテンプレート(GitHub)

Kotlin(英語)

他のスクリプトエンジンを統合するための基本的なルールは、ScriptEngine および Invocable インターフェースを実装する必要があるということです。
要件

クラスパスにスクリプトエンジンが必要です。詳細はスクリプトエンジンによって異なります。

  • Nashorn(英語) JavaScriptエンジンにはJava 8+が付属しています。利用可能な最新の更新リリースを使用することを強くお勧めします。

  • JRuby(英語) は、Rubyサポートの依存関係として追加する必要があります。

  • Jython(英語) は、Pythonサポートの依存関係として追加する必要があります。

  • Kotlinスクリプトをサポートするには、org.jetbrains.kotlin:kotlin-script-util 依存関係と org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory 行を含む META-INF/services/javax.script.ScriptEngineFactory ファイルを追加する必要があります。詳細については、この例(GitHub) を参照してください。

スクリプトテンプレートライブラリが必要です。Javascriptでこれを行う1つの方法は、WebJars(英語) を使用することです。

スクリプトテンプレート

ScriptTemplateConfigurer Beanを宣言して、使用するスクリプトエンジン、ロードするスクリプトファイル、テンプレートをレンダリングするために呼び出す関数などを指定できます。次の例では、MustacheテンプレートとNashorn JavaScriptエンジンを使用しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.scriptTemplate();
    }

    @Bean
    public ScriptTemplateConfigurer configurer() {
        ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
        configurer.setEngineName("nashorn");
        configurer.setScripts("mustache.js");
        configurer.setRenderObject("Mustache");
        configurer.setRenderFunction("render");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }

    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("mustache.js")
        renderObject = "Mustache"
        renderFunction = "render"
    }
}

render 関数は、次のパラメーターで呼び出されます。

  • String template : テンプレートの内容

  • Map model : ビューモデル

  • RenderingContext renderingContext : アプリケーションコンテキスト、ロケール、テンプレートローダー、およびURLへのアクセスを提供する RenderingContext (Javadoc) (5.0以降)

Mustache.render() はこの署名とネイティブに互換性があるため、直接呼び出すことができます。

テンプレートテクノロジでカスタマイズが必要な場合は、カスタムレンダリング機能を実装するスクリプトを提供できます。例:Handlerbars(英語) は、使用する前にテンプレートをコンパイルする必要があり、サーバーサイドスクリプトエンジンでは利用できないブラウザー機能をエミュレートするために、ポリフィル(英語) が必要です。次の例は、カスタムレンダリング関数を設定する方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.scriptTemplate();
    }

    @Bean
    public ScriptTemplateConfigurer configurer() {
        ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
        configurer.setEngineName("nashorn");
        configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
        configurer.setRenderFunction("render");
        configurer.setSharedEngine(false);
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }

    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("polyfill.js", "handlebars.js", "render.js")
        renderFunction = "render"
        isSharedEngine = false
    }
}
Nashornで実行されているHandlebarsやReactなど、同時実行用に設計されていないテンプレートライブラリで非スレッドセーフスクリプトエンジンを使用する場合は、sharedEngine プロパティを false に設定する必要があります。その場合、このバグ(英語) のため、Java SE 8アップデート60が必要ですが、通常はいずれの場合も最新のJava SEパッチリリースを使用することをお勧めします。

polyfill.js は、次のスニペットが示すように、Handlebarsが正しく実行するために必要な window オブジェクトのみを定義します。

var window = {};

この基本的な render.js 実装は、テンプレートを使用する前にコンパイルします。また、本番対応の実装では、キャッシュされたテンプレートまたはプリコンパイルされたテンプレートを保存して再利用する必要があります。これは、必要なカスタマイズ(たとえば、テンプレートエンジンの構成の管理)だけでなく、スクリプト側でも実行できます。次の例は、テンプレートのコンパイル方法を示しています。

function render(template, model) {
    var compiledTemplate = Handlebars.compile(template);
    return compiledTemplate(model);
}

その他の構成例については、Spring Framework単体テスト、Java(GitHub) 、およびリソース(GitHub) を参照してください。

1.9.4. JSONとXML

コンテンツ交渉の目的では、クライアントが要求するコンテンツタイプに応じて、HTMLテンプレートを使用してモデルをレンダリングするか、他の形式(JSONやXMLなど)を交互にレンダリングできると便利です。これをサポートするために、Spring WebFluxは HttpMessageWriterViewを提供します。これを使用して、Jackson2JsonEncoder, Jackson2SmileEncoderJaxb2XmlEncoderなど、spring-webから利用可能なコーデックをプラグインすることができます。

他のビューテクノロジーとは異なり、HttpMessageWriterViewViewResolver を必要とせず、代わりにデフォルトビューとして設定されます。異なる HttpMessageWriter インスタンスまたは Encoder インスタンスをラップして、このようなデフォルトビューを1つ以上構成できます。要求されたコンテンツタイプに一致するものが実行時に使用されます。

ほとんどの場合、モデルには複数の属性が含まれます。どれを直列化するかを決定するために、レンダリングに使用するモデル属性の名前で HttpMessageWriterView を構成できます。モデルに含まれる属性が1つだけの場合、その属性が使用されます。

1.10. HTTPキャッシング

HTTPキャッシングは、Webアプリケーションのパフォーマンスを大幅に改善できます。HTTPキャッシングは、Cache-Control 応答ヘッダーと、Last-ModifiedETagなどの後続の条件付き要求ヘッダーを中心に展開します。 Cache-Control は、プライベート(ブラウザなど)およびパブリック(プロキシなど)のキャッシュが、応答をキャッシュして再利用する方法をアドバイスします。コンテンツが変更されていない場合、ETag ヘッダーを使用して、本文のない304(NOT_MODIFIED)になる条件付きリクエストを作成します。 ETag は、Last-Modified ヘッダーの後継として洗練されたものと見なすことができます。

このセクションでは、Spring WebFluxで使用可能なHTTPキャッシュ関連オプションについて説明します。

1.10.1. CacheControl

CacheControl (Javadoc) は、Cache-Control ヘッダーに関連する設定の構成をサポートし、多くの場所で引数として受け入れられます。

RFC 7234(英語) Cache-Control 応答ヘッダーのすべての可能なディレクティブを記述しますが、CacheControl タイプは、次の例に示すように、一般的なシナリオに焦点を当てたユースケース指向のアプローチを取ります。

Java
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
Kotlin
// Cache for an hour - "Cache-Control: max-age=3600"
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)

// Prevent caching - "Cache-Control: no-store"
val ccNoStore = CacheControl.noStore()

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()

1.10.2. コントローラー

コントローラーは、HTTPキャッシングの明示的なサポートを追加できます。リソースの lastModified または ETag 値は、条件付きリクエストヘッダーと比較する前に計算する必要があるため、そうすることをお勧めします。次の例に示すように、コントローラーは ETag および Cache-Control 設定を ResponseEntityに追加できます。

Java
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {

    Book book = findBook(id);
    String version = book.getVersion();

    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book);
}
Kotlin
@GetMapping("/book/{id}")
fun showBook(@PathVariable id: Long): ResponseEntity<Book> {

    val book = findBook(id)
    val version = book.getVersion()

    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book)
}

前述の例では、条件付きリクエストヘッダーとの比較でコンテンツが変更されていないことが示された場合、空のボディを含む304(NOT_MODIFIED)応答が送信されます。それ以外の場合、ETag および Cache-Control ヘッダーが応答に追加されます。

次の例に示すように、コントローラーの条件付き要求ヘッダーに対してチェックを行うこともできます。

Java
@RequestMapping
public String myHandleMethod(ServerWebExchange exchange, Model model) {

    long eTag = ... (1)

    if (exchange.checkNotModified(eTag)) {
        return null; (2)
    }

    model.addAttribute(...); (3)
    return "myViewName";
}
1アプリケーション固有の計算。
2応答は304(NOT_MODIFIED)に設定されています。それ以上の処理はありません。
3要求処理を続行します。
Kotlin
@RequestMapping
fun myHandleMethod(exchange: ServerWebExchange, model: Model): String? {

    val eTag: Long = ... (1)

    if (exchange.checkNotModified(eTag)) {
        return null(2)
    }

    model.addAttribute(...) (3)
    return "myViewName"
}
1アプリケーション固有の計算。
2応答は304(NOT_MODIFIED)に設定されています。それ以上の処理はありません。
3要求処理を続行します。

eTag 値、lastModified 値、またはその両方に対して条件付き要求をチェックするための3つのバリアントがあります。条件付き GET および HEAD 要求の場合、応答を304(NOT_MODIFIED)に設定できます。条件付き POST, PUTおよび DELETEの場合、代わりに応答を412(PRECONDITION_FAILED)に設定して、同時変更を防ぐことができます。

1.10.3. 静的リソース

最適なパフォーマンスを得るには、Cache-Control と条件付き応答ヘッダーで静的リソースを提供する必要があります。静的リソースの構成に関するセクションを参照してください。

1.11. WebFlux構成

WebFlux Java構成は、アノテーション付きコントローラーまたは機能エンドポイントで要求を処理するために必要なコンポーネントを宣言し、構成をカスタマイズするためのAPIを提供します。つまり、Java構成によって作成された基礎となるBeanを理解する必要はありません。ただし、理解したい場合は、WebFluxConfigurationSupport で表示するか、特別なBeanタイプの内容について詳しく読むことができます。

構成APIにはない高度なカスタマイズについては、拡張構成モードを使用して構成を完全に制御できます。

1.11.1. WebFlux構成の有効化

次の例に示すように、Java configで @EnableWebFlux アノテーションを使用できます。

Java
@Configuration
@EnableWebFlux
public class WebConfig {
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig

The preceding example registers a number of Spring WebFlux infrastructure beans and adapts to dependencies available on the classpath — for JSON, XML, and others.

1.11.2. WebFlux構成API

Java構成では、次の例に示すように、WebFluxConfigurer インターフェースを実装できます。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    // Implement configuration methods...
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    // Implement configuration methods...
}

1.11.3. 変換、フォーマット

デフォルトでは、@NumberFormat および @DateTimeFormat アノテーションのサポートを含む、Number および Date タイプのフォーマッターがインストールされます。Joda-Timeがクラスパスに存在する場合、Joda-Timeフォーマットライブラリの完全サポートもインストールされます。

次の例は、カスタムフォーマッタとコンバーターを登録する方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
    }

}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun addFormatters(registry: FormatterRegistry) {
        // ...
    }
}
FormatterRegistrar 実装をいつ使用するかの詳細については、 FormatterRegistrar SPIおよび FormattingConversionServiceFactoryBean を参照してください。

1.11.4. 検証

デフォルトでは、Bean バリデーションがクラスパスに存在する場合(たとえば、Hibernateバリデーター)、LocalValidatorFactoryBean は、@Controller メソッド引数の @Valid および Validated で使用するためのグローバルバリデーターとして登録されます。

Java構成では、次の例に示すように、グローバル Validator インスタンスをカスタマイズできます。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public Validator getValidator(); {
        // ...
    }

}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun getValidator(): Validator {
        // ...
    }

}

次の例に示すように、Validator 実装をローカルに登録することもできます。

Java
@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(new FooValidator());
    }

}
Kotlin
@Controller
class MyController {

    @InitBinder
    protected fun initBinder(binder: WebDataBinder) {
        binder.addValidators(FooValidator())
    }
}
LocalValidatorFactoryBean をどこかに挿入する必要がある場合は、Beanを作成し、@Primary でマークして、MVC configで宣言されたものとの競合を回避します。

1.11.5. コンテンツタイプリゾルバー

Spring WebFluxが、リクエストから @Controller インスタンスのリクエストされたメディアタイプを決定する方法を設定できます。デフォルトでは、Accept ヘッダーのみがチェックされますが、クエリパラメーターベースの戦略を有効にすることもできます。

次の例は、要求されたコンテンツタイプの解決をカスタマイズする方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureContentTypeResolver(builder: RequestedContentTypeResolverBuilder) {
        // ...
    }
}

1.11.6. HTTPメッセージコーデック

次の例は、要求と応答の本文の読み取りおよび書き込み方法をカスタマイズする方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
        // ...
    }
}

ServerCodecConfigurer は、デフォルトのリーダーとライターのセットを提供します。これを使用して、リーダーとライターを追加したり、デフォルトのものをカスタマイズしたり、デフォルトのものを完全に置き換えたりできます。

Jackson JSONおよびXMLの場合、 Jackson2ObjectMapperBuilder (Javadoc) の使用を検討してください。 Jackson2ObjectMapperBuilder (Javadoc) は、Jacksonのデフォルトプロパティを次のようにカスタマイズします。

また、次の既知のモジュールがクラスパスで検出された場合、自動的に登録します。

1.11.7. リゾルバーを表示

次の例は、ビューの解像度を構成する方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        // ...
    }
}

ViewResolverRegistry には、Spring Frameworkが統合するビューテクノロジーへのショートカットがあります。次の例では、FreeMarkerを使用しています(これには、基になるFreeMarkerビューテクノロジの構成も必要です)

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {


    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.freeMarker();
    }

    // Configure Freemarker...

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("classpath:/templates");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.freeMarker()
    }

    // Configure Freemarker...

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("classpath:/templates")
    }
}

次の例に示すように、ViewResolver 実装をプラグインすることもできます。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {


    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        ViewResolver resolver = ... ;
        registry.viewResolver(resolver);
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        val resolver: ViewResolver = ...
        registry.viewResolver(resolver
    }
}

コンテンツ交渉をサポートし、ビューリゾルバー(HTML以外)を介して他の形式をレンダリングするには、spring-webから利用可能なコーデックのいずれかを受け入れる HttpMessageWriterView 実装に基づいて、1つまたは複数の既定のビューを構成できます。次の例は、その方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {


    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.freeMarker();

        Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
        registry.defaultViews(new HttpMessageWriterView(encoder));
    }

    // ...
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {


    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.freeMarker()

        val encoder = Jackson2JsonEncoder()
        registry.defaultViews(HttpMessageWriterView(encoder))
    }

    // ...
}

Spring WebFluxと統合されているビューテクノロジーの詳細については、ビューテクノロジーを参照してください。

1.11.8. 静的リソース

このオプションは、 Resource (Javadoc) ベースの場所のリストから静的リソースを提供する便利な方法を提供します。

次の例では、/resourcesで始まるリクエストを指定すると、相対パスを使用して、クラスパス上の /static に関連する静的リソースを見つけて処理します。ブラウザーのキャッシュを最大限に使用し、ブラウザーが行うHTTPリクエストを削減するために、リソースには1年間の有効期限があります。 Last-Modified ヘッダーも評価され、存在する場合は 304 ステータスコードが返されます。次のリストに例を示します。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
            .addResourceLocations("/public", "classpath:/static/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
    }

}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public", "classpath:/static/")
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
    }
}

リソースハンドラーは、 ResourceResolver (Javadoc) 実装と ResourceTransformer (Javadoc) 実装のチェーンもサポートしています。これらを使用して、最適化されたリソースを操作するためのツールチェーンを作成できます。

コンテンツ、固定アプリケーションバージョン、またはその他の情報から計算されたMD5ハッシュに基づいて、バージョン管理されたリソースURLに VersionResourceResolver を使用できます。 ContentVersionStrategy (MD5ハッシュ)は、いくつかの注目すべき例外(モジュールローダーで使用されるJavaScriptリソースなど)がある場合に適しています。

次の例は、Java構成で VersionResourceResolver を使用する方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
    }

}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(VersionResourceResolver().addContentVersionStrategy("/**"))
    }

}

ResourceUrlProvider を使用してURLを書き換え、リゾルバーとトランスフォーマーの完全なチェーンを適用できます(たとえば、バージョンを挿入するため)。WebFlux構成は、ResourceUrlProvider を提供するため、他に注入することができます。

Spring MVCとは異なり、現在、WebFluxでは、リゾルバーとトランスフォーマーのノンブロッキングチェーンを使用できるビューテクノロジーがないため、静的リソースURLを透過的に書き換える方法はありません。ローカルリソースのみを提供する場合、回避策は、ResourceUrlProvider を直接(たとえば、カスタム要素を介して)使用してブロックすることです。

EncodedResourceResolver (たとえば、Gzip、Brotliエンコード)と VersionedResourceResolverの両方を使用する場合、エンコードされていないファイルに基づいてコンテンツベースのバージョンが常に確実に計算されるように、この順序で登録する必要があります。

WebJars(英語) は、org.webjars:webjars-locator-core ライブラリがクラスパスに存在するときに自動的に登録される WebJarsResourceResolver でもサポートされます。リゾルバーは、jarのバージョンを含むようにURLを書き換えることができ、バージョンのない受信URL(たとえば、/jquery/jquery.min.js から /jquery/1.2.0/jquery.min.jsなど)と照合することもできます。

1.11.9. パスマッチング

パスマッチングに関連するオプションをカスタマイズできます。個々のオプションの詳細については、 PathMatchConfigurer (Javadoc) javadocを参照してください。次の例は、PathMatchConfigurerの使用方法を示しています。

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
            .setUseCaseSensitiveMatch(true)
            .setUseTrailingSlashMatch(false)
            .addPathPrefix("/api",
                    HandlerTypePredicate.forAnnotation(RestController.class));
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    @Override
    fun configurePathMatch(configurer: PathMatchConfigurer) {
        configurer
            .setUseCaseSensitiveMatch(true)
            .setUseTrailingSlashMatch(false)
            .addPathPrefix("/api",
                    HandlerTypePredicate.forAnnotation(RestController::class.java))
    }
}

Spring WebFluxは、デコードされたパスセグメント値にアクセスするために、RequestPath と呼ばれる要求パスの解析された表現に依存し、セミコロンコンテンツ(つまり、パスまたはマトリックス変数)を削除します。つまり、Spring MVCとは異なり、要求パスをデコードするか、パスマッチングのためにセミコロンコンテンツを削除するかを指定する必要はありません。

Spring WebFluxはまた、また、されているSpring MVC、とは異なり、接尾辞パターンマッチングをサポートしていないお勧めします、それに離れ依存から移動します。

1.11.10. 拡張構成モード

@EnableWebFluxDelegatingWebFluxConfiguration をインポートします:

  • WebFluxアプリケーションにデフォルトのSpring構成を提供する

  • WebFluxConfigurer 実装を検出して委譲し、その構成をカスタマイズします。

拡張モードの場合、次の例に示すように、WebFluxConfigurerを実装する代わりに、@EnableWebFlux を削除して DelegatingWebFluxConfiguration から直接拡張できます。

Java
@Configuration
public class WebConfig extends DelegatingWebFluxConfiguration {

    // ...
}
Kotlin
@Configuration
class WebConfig : DelegatingWebFluxConfiguration {

    // ...
}

既存のメソッドを WebConfigに保持できますが、ベースクラスからのBean宣言をオーバーライドしても、クラスパスに他の WebMvcConfigurer 実装をいくつでも持つことができます。

1.12. HTTP/2

サーブレット4コンテナーはHTTP / 2をサポートするために必要であり、Spring Framework 5はサーブレットAPI 4と互換性があります。プログラミングモデルの観点からは、アプリケーションが行う必要のある特定のものはありません。ただし、サーバー構成に関連する考慮事項があります。詳細については、HTTP / 2 wikiページ(GitHub) を参照してください。

現在、Spring WebFluxはNettyでHTTP / 2をサポートしていません。また、リソースをプログラムでクライアントにプッシュすることもサポートされていません。

2. WebClient

Spring WebFluxには、HTTPリクエスト用のリアクティブでノンブロッキングの WebClient が含まれています。クライアントには、宣言的構成のためのリアクティブ型を持つ機能的で流なAPIがあります。リアクティブライブラリを参照してください。WebFluxクライアントとサーバーは、同じノンブロッキングコーデックに依存して、リクエストおよびレスポンスコンテンツをエンコードおよびデコードします。

内部的に WebClient はHTTPクライアントライブラリに委譲します。デフォルトでは、Reactor Netty(GitHub) を使用し、Jetty リアクティブHttpClient(GitHub) の組み込みサポートがあり、その他は ClientHttpConnectorを介してプラグインできます。

2.1. 構成

WebClient を作成する最も簡単な方法は、静的ファクトリーメソッドの1つを使用することです。

  • WebClient.create()

  • WebClient.create(String baseUrl)

上記のメソッドは、Reactor Netty HttpClient をデフォルト設定で使用し、io.projectreactor.netty:reactor-netty がクラスパス上にあることを想定しています。

WebClient.builder() を追加オプションとともに使用することもできます。

  • uriBuilderFactory : ベースURLとして使用するために UriBuilderFactory をカスタマイズしました。

  • defaultHeader : すべてのリクエストのヘッダー。

  • defaultCookie : リクエストごとのCookie。

  • defaultRequest : Consumer ですべてのリクエストをカスタマイズします。

  • filter : すべてのリクエストのクライアントフィルター。

  • exchangeStrategies : HTTPメッセージリーダー/ライターのカスタマイズ。

  • clientConnector : HTTPクライアントライブラリの設定。

次の例では、HTTPコーデックを構成しています。

Java
WebClient client = WebClient.builder()
        .exchangeStrategies(builder -> {
                return builder.codecs(codecConfigurer -> {
                    //...
                });
        })
        .build();
Kotlin
val webClient = WebClient.builder()
        .exchangeStrategies { strategies ->
            strategies.codecs {
                //...
            }
        }
        .build()

一度構築されると、WebClient インスタンスは不変です。ただし、次の例に示すように、元のインスタンスに影響を与えることなく、クローンを作成して変更されたコピーを作成できます。

Java
WebClient client1 = WebClient.builder()
        .filter(filterA).filter(filterB).build();

WebClient client2 = client1.mutate()
        .filter(filterC).filter(filterD).build();

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD
Kotlin
val client1 = WebClient.builder()
        .filter(filterA).filter(filterB).build()

val client2 = client1.mutate()
        .filter(filterC).filter(filterD).build()

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD

2.1.1. MaxInMemorySize

Spring WebFluxは、アプリケーションメモリの課題を回避するために、コーデックのメモリ内データのバッファリングの制限を構成します。デフォルトでは、これは256KBに設定されており、ユースケースに十分でない場合は、次のように表示されます。

org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer

次のコードサンプルを使用して、すべてのデフォルトコーデックでこの制限を構成できます。

Java
WebClient webClient = WebClient.builder()
        .exchangeStrategies(builder ->
            builder.codecs(codecs ->
                codecs.defaultCodecs().maxInMemorySize(2 * 1024 * 1024)
            )
        )
        .build();
Kotlin
val webClient = WebClient.builder()
    .exchangeStrategies { builder ->
            builder.codecs {
                it.defaultCodecs().maxInMemorySize(2 * 1024 * 1024)
            }
    }
    .build()

2.1.2. Reactor Netty

Reactor Netty設定をカスタマイズするには、事前に構成された HttpClientを提供するだけです:

Java
HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);

WebClient webClient = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();
Kotlin
val httpClient = HttpClient.create().secure { ... }

val webClient = WebClient.builder()
    .clientConnector(ReactorClientHttpConnector(httpClient))
    .build()
リソース

デフォルトでは、HttpClient は、イベントループスレッドや接続プールなど、reactor.netty.http.HttpResourcesに保持されているグローバルReactor Nettyリソースに参加します。これは、イベントループの同時実行には固定の共有リソースが優先されるため、推奨されるモードです。このモードでは、プロセスが終了するまでグローバルリソースがアクティブのままになります。

サーバーがプロセスとタイミングを合わせている場合、通常、明示的なシャットダウンの必要はありません。ただし、サーバーがインプロセスを開始または停止できる場合(たとえば、WARとしてデプロイされたSpring MVCアプリケーション)、globalResources=true (デフォルト)で ReactorResourceFactory タイプのSpring管理Beanを宣言して、Reactor Nettyがグローバルであることを確認できます。次の例に示すように、Spring ApplicationContext が閉じられると、リソースはシャットダウンされます。

Java
@Bean
public ReactorResourceFactory reactorResourceFactory() {
    return new ReactorResourceFactory();
}
Kotlin
@Bean
fun reactorResourceFactory() = ReactorResourceFactory()

グローバルReactor Nettyリソースに参加しないことも選択できます。ただし、このモードでは、次の例に示すように、すべてのReactor Nettyクライアントおよびサーバーインスタンスが共有リソースを使用することを保証する負担があります。

Java
@Bean
public ReactorResourceFactory resourceFactory() {
    ReactorResourceFactory factory = new ReactorResourceFactory();
    factory.setUseGlobalResources(false); (1)
    return factory;
}

@Bean
public WebClient webClient() {

    Function<HttpClient, HttpClient> mapper = client -> {
        // Further customizations...
    };

    ClientHttpConnector connector =
            new ReactorClientHttpConnector(resourceFactory(), mapper); (2)

    return WebClient.builder().clientConnector(connector).build(); (3)
}
1グローバルなリソースから独立したリソースを作成します。
2リソースファクトリで ReactorClientHttpConnector コンストラクターを使用します。
3コネクタを WebClient.Builderに差し込みます。
Kotlin
@Bean
fun resourceFactory() = ReactorResourceFactory().apply {
    isUseGlobalResources = false (1)
}

@Bean
fun webClient(): WebClient {

    val mapper: (HttpClient) -> HttpClient = {
        // Further customizations...
    }

    val connector = ReactorClientHttpConnector(resourceFactory(), mapper) (2)

    return WebClient.builder().clientConnector(connector).build() (3)
}
1グローバルなリソースから独立したリソースを作成します。
2リソースファクトリで ReactorClientHttpConnector コンストラクターを使用します。
3コネクタを WebClient.Builderに差し込みます。
タイムアウト

接続タイムアウトを構成するには:

Java
import io.netty.channel.ChannelOption;

HttpClient httpClient = HttpClient.create()
        .tcpConfiguration(client ->
                client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000));
Kotlin
import io.netty.channel.ChannelOption

val httpClient = HttpClient.create()
        .tcpConfiguration { it.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)}

読み取りおよび/または書き込みタイムアウト値を設定するには:

Java
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;

HttpClient httpClient = HttpClient.create()
        .tcpConfiguration(client ->
                client.doOnConnected(conn -> conn
                        .addHandlerLast(new ReadTimeoutHandler(10))
                        .addHandlerLast(new WriteTimeoutHandler(10))));
Kotlin
import io.netty.handler.timeout.ReadTimeoutHandler
import io.netty.handler.timeout.WriteTimeoutHandler

val httpClient = HttpClient.create().tcpConfiguration {
    it.doOnConnected { conn -> conn
            .addHandlerLast(ReadTimeoutHandler(10))
            .addHandlerLast(WriteTimeoutHandler(10))
    }
}

2.1.3. Jetty

次の例は、Jetty HttpClient 設定をカスタマイズする方法を示しています。

Java
HttpClient httpClient = new HttpClient();
httpClient.setCookieStore(...);
ClientHttpConnector connector = new JettyClientHttpConnector(httpClient);

WebClient webClient = WebClient.builder().clientConnector(connector).build();
Kotlin
val httpClient = HttpClient()
httpClient.cookieStore = ...
val connector = JettyClientHttpConnector(httpClient)

val webClient = WebClient.builder().clientConnector(connector).build();

デフォルトでは、HttpClient は独自のリソース(Executor, ByteBufferPool, Scheduler)を作成し、プロセスが終了するか stop() が呼び出されるまでアクティブのままになります。

次の例に示すように、タイプ JettyResourceFactoryのSpring管理Beanを宣言することにより、Jettyクライアント(およびサーバー)の複数のインスタンス間でリソースを共有し、Spring ApplicationContext が閉じられたときにリソースを確実にシャットダウンできます。

Java
@Bean
public JettyResourceFactory resourceFactory() {
    return new JettyResourceFactory();
}

@Bean
public WebClient webClient() {

    HttpClient httpClient = new HttpClient();
    // Further customizations...

    ClientHttpConnector connector =
            new JettyClientHttpConnector(httpClient, resourceFactory()); (1)

    return WebClient.builder().clientConnector(connector).build(); (2)
}
1リソースファクトリで JettyClientHttpConnector コンストラクターを使用します。
2コネクタを WebClient.Builderに差し込みます。
Kotlin
@Bean
fun resourceFactory() = JettyResourceFactory()

@Bean
fun webClient(): WebClient {

    val httpClient = HttpClient()
    // Further customizations...

    val connector = JettyClientHttpConnector(httpClient, resourceFactory()) (1)

    return WebClient.builder().clientConnector(connector).build() (2)
}
1リソースファクトリで JettyClientHttpConnector コンストラクターを使用します。
2コネクタを WebClient.Builderに差し込みます。

2.2. retrieve()

retrieve() メソッドは、応答本文を取得してデコードする最も簡単な方法です。次の例は、その方法を示しています。

Java
WebClient client = WebClient.create("https://example.org");

Mono<Person> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .bodyToMono(Person.class);
Kotlin
val client = WebClient.create("https://example.org")

val result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .awaitBody<Person>()

次の例に示すように、応答からデコードされたオブジェクトのストリームを取得することもできます。

Java
Flux<Quote> result = client.get()
        .uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
        .retrieve()
        .bodyToFlux(Quote.class);
Kotlin
val result = client.get()
        .uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
        .retrieve()
        .bodyToFlow<Quote>()

デフォルトでは、4xxまたは5xxステータスコードでの応答は、WebClientResponseException または WebClientResponseException.BadRequest, WebClientResponseException.NotFoundなどのHTTPステータス固有のサブクラスの1つになります。次の例に示すように、onStatus メソッドを使用して、結果の例外をカスタマイズすることもできます。

Java
Mono<Person> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, response -> ...)
        .onStatus(HttpStatus::is5xxServerError, response -> ...)
        .bodyToMono(Person.class);
Kotlin
val result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError) { ... }
        .onStatus(HttpStatus::is5xxServerError) { ... }
        .awaitBody<Person>()

onStatus を使用する場合、応答にコンテンツがあると予想される場合、onStatus コールバックはそれを消費する必要があります。そうでない場合は、コンテンツが自動的に排出され、リソースが解放されます。

2.3. exchange()

exchange() メソッドは、retrieve メソッドよりも多くの制御を提供します。次の例は retrieve() と同等ですが、ClientResponseへのアクセスも提供します。

Java
Mono<Person> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .exchange()
        .flatMap(response -> response.bodyToMono(Person.class));
Kotlin
val result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .awaitExchange()
        .awaitBody<Person>()

このレベルでは、完全な ResponseEntityを作成することもできます。

Java
Mono<ResponseEntity<Person>> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .exchange()
        .flatMap(response -> response.toEntity(Person.class));
Kotlin
val result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .awaitExchange()
        .toEntity<Person>()

retrieve()とは異なり) exchange()では、4xxおよび5xx応答の自動エラー信号がないことに注意してください。ステータスコードを確認し、続行する方法を決定する必要があります。

retrieve()とは異なり、exchange(), it is the responsibility of the application to consume any response content regardless of the scenario (success, error, unexpected data, etc). Not doing so can cause a memory leak. The Javadoc for `ClientResponse を使用すると、ボディを消費するために使用可能なすべてのオプションがリストされます。 exchange() を使用する正当な理由がない限り、一般に retrieve() を使用することをお勧めします。exchange() を使用すると、応答をどのようにまたはどのように消費するかを決定する前に応答ステータスとヘッダーを確認できます

2.4. リクエストボディー

要求本体は、次の例に示すように、Mono やKotlinコルーチン Deferred など、ReactiveAdapterRegistryによって処理される任意の非同期タイプからエンコードできます。

Java
Mono<Person> personMono = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body(personMono, Person.class)
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val personDeferred: Deferred<Person> = ...

client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body<Person>(personDeferred)
        .retrieve()
        .awaitBody<Unit>()

次の例に示すように、オブジェクトのストリームをエンコードすることもできます。

Java
Flux<Person> personFlux = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_STREAM_JSON)
        .body(personFlux, Person.class)
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val people: Flow<Person> = ...

client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body(people)
        .retrieve()
        .awaitBody<Unit>()

または、実際の値がある場合は、次の例に示すように、bodyValue ショートカットメソッドを使用できます。

Java
Person person = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(person)
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val person: Person = ...

client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(person)
        .retrieve()
        .awaitBody<Unit>()

2.4.1. フォームデータ

フォームデータを送信するには、MultiValueMap<String, String> を本文として提供できます。コンテンツは FormHttpMessageWriterによって application/x-www-form-urlencoded に自動的に設定されることに注意してください。次の例は、MultiValueMap<String, String>の使用方法を示しています。

Java
MultiValueMap<String, String> formData = ... ;

Mono<Void> result = client.post()
        .uri("/path", id)
        .bodyValue(formData)
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val formData: MultiValueMap<String, String> = ...

client.post()
        .uri("/path", id)
        .bodyValue(formData)
        .retrieve()
        .awaitBody<Unit>()

次の例に示すように、BodyInsertersを使用してインラインでフォームデータを提供することもできます。

Java
import static org.springframework.web.reactive.function.BodyInserters.*;

Mono<Void> result = client.post()
        .uri("/path", id)
        .body(fromFormData("k1", "v1").with("k2", "v2"))
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
import org.springframework.web.reactive.function.BodyInserters.*

client.post()
        .uri("/path", id)
        .body(fromFormData("k1", "v1").with("k2", "v2"))
        .retrieve()
        .awaitBody<Unit>()

2.4.2. マルチパートデータ

マルチパートデータを送信するには、パーツのコンテンツを表す Object インスタンスまたはパーツのコンテンツとヘッダーを表す HttpEntity インスタンスのいずれかである MultiValueMap<String, ?> を提供する必要があります。 MultipartBodyBuilder は、マルチパートリクエストを準備するための便利なAPIを提供します。次の例は、MultiValueMap<String, ?>を作成する方法を示しています。

Java
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart1", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
builder.part("myPart", part); // Part from a server request

MultiValueMap<String, HttpEntity<?>> parts = builder.build();
Kotlin
val builder = MultipartBodyBuilder().apply {
    part("fieldPart", "fieldValue")
    part("filePart1", new FileSystemResource("...logo.png"))
    part("jsonPart", new Person("Jason"))
    part("myPart", part) // Part from a server request
}

val parts = builder.build()

ほとんどの場合、各パーツに Content-Type を指定する必要はありません。コンテンツタイプは、シリアライズするために選択された HttpMessageWriter に基づいて、または Resourceの場合はファイル拡張子に基づいて自動的に決定されます。必要に応じて、オーバーロードされたビルダー part メソッドのいずれかを使用して、各パーツに使用する MediaType を明示的に提供できます。

MultiValueMap を準備したら、次の例に示すように、WebClient に渡す最も簡単な方法は body メソッドを使用することです。

Java
MultipartBodyBuilder builder = ...;

Mono<Void> result = client.post()
        .uri("/path", id)
        .body(builder.build())
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val builder: MultipartBodyBuilder = ...

client.post()
        .uri("/path", id)
        .body(builder.build())
        .retrieve()
        .awaitBody<Unit>()

MultiValueMap に少なくとも1つのString 以外の値(通常のフォームデータ(つまり application/x-www-form-urlencoded)を表す場合もある)が含まれている場合、Content-Typemultipart/form-dataに設定する必要はありません。これは、MultipartBodyBuilderを使用する場合に常に当てはまり、HttpEntity ラッパーが保証されます。

MultipartBodyBuilderの代替として、次の例に示すように、組み込みの BodyInsertersを介して、インラインスタイルのマルチパートコンテンツを提供することもできます。

Java
import static org.springframework.web.reactive.function.BodyInserters.*;

Mono<Void> result = client.post()
        .uri("/path", id)
        .body(fromMultipartData("fieldPart", "value").with("filePart", resource))
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
import org.springframework.web.reactive.function.BodyInserters.*

client.post()
        .uri("/path", id)
        .body(fromMultipartData("fieldPart", "value").with("filePart", resource))
        .retrieve()
        .awaitBody<Unit>()

2.5. クライアントフィルター

次の例に示すように、要求を傍受および変更するために、WebClient.Builder を介してクライアントフィルター(ExchangeFilterFunction)を登録できます。

Java
WebClient client = WebClient.builder()
        .filter((request, next) -> {

            ClientRequest filtered = ClientRequest.from(request)
                    .header("foo", "bar")
                    .build();

            return next.exchange(filtered);
        })
        .build();
Kotlin
val client = WebClient.builder()
        .filter { request, next ->

            val filtered = ClientRequest.from(request)
                    .header("foo", "bar")
                    .build()

            next.exchange(filtered)
        }
        .build()

これは、認証などの横断的な関心事に使用できます。次の例では、静的ファクトリー方式による基本認証にフィルターを使用しています。

Java
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;

WebClient client = WebClient.builder()
        .filter(basicAuthentication("user", "password"))
        .build();
Kotlin
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication

val client = WebClient.builder()
        .filter(basicAuthentication("user", "password"))
        .build()

フィルタはすべてのリクエストにグローバルに適用されます。特定のリクエストのフィルターの動作を変更するには、次の例に示すように、チェーンのすべてのフィルターからアクセスできるリクエスト属性を ClientRequest に追加します。

Java
WebClient client = WebClient.builder()
        .filter((request, next) -> {
            Optional<Object> usr = request.attribute("myAttribute");
            // ...
        })
        .build();

client.get().uri("https://example.org/")
        .attribute("myAttribute", "...")
        .retrieve()
        .bodyToMono(Void.class);

    }
Kotlin
val client = WebClient.builder()
            .filter { request, _ ->
        val usr = request.attributes()["myAttribute"];
        // ...
    }.build()

    client.get().uri("https://example.org/")
            .attribute("myAttribute", "...")
            .retrieve()
            .awaitBody<Unit>()

既存の WebClientを複製したり、新しいフィルターを挿入したり、すでに登録されているフィルターを削除したりすることもできます。次の例では、インデックス0に基本認証フィルターを挿入します。

Java
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;

WebClient client = webClient.mutate()
        .filters(filterList -> {
            filterList.add(0, basicAuthentication("user", "password"));
        })
        .build();
Kotlin
val client = webClient.mutate()
        .filters { it.add(0, basicAuthentication("user", "password")) }
        .build()

2.6. 同期使用

WebClient は、結果の最後でブロックすることにより、同期スタイルで使用できます。

Java
Person person = client.get().uri("/person/{id}", i).retrieve()
    .bodyToMono(Person.class)
    .block();

List<Person> persons = client.get().uri("/persons").retrieve()
    .bodyToFlux(Person.class)
    .collectList()
    .block();
Kotlin
val person = runBlocking {
    client.get().uri("/person/{id}", i).retrieve()
            .awaitBody<Person>()
}

val persons = runBlocking {
    client.get().uri("/persons").retrieve()
            .bodyToFlow<Person>()
            .toList()
}

ただし、複数の呼び出しを行う必要がある場合は、各応答を個別にブロックするのを避け、代わりに結合された結果を待つ方が効率的です。

Java
Mono<Person> personMono = client.get().uri("/person/{id}", personId)
        .retrieve().bodyToMono(Person.class);

Mono<List<Hobby>> hobbiesMono = client.get().uri("/person/{id}/hobbies", personId)
        .retrieve().bodyToFlux(Hobby.class).collectList();

Map<String, Object> data = Mono.zip(personMono, hobbiesMono, (person, hobbies) -> {
            Map<String, String> map = new LinkedHashMap<>();
            map.put("person", person);
            map.put("hobbies", hobbies);
            return map;
        })
        .block();
Kotlin
val data = runBlocking {
        val personDeferred = async {
            client.get().uri("/person/{id}", personId)
                    .retrieve().awaitBody<Person>()
        }

        val hobbiesDeferred = async {
            client.get().uri("/person/{id}/hobbies", personId)
                    .retrieve().bodyToFlow<Hobby>().toList()
        }

        mapOf("person" to personDeferred.await(), "hobbies" to hobbiesDeferred.await())
    }

上記は単なる一例です。多くのリモート呼び出しを作成するリアクティブパイプラインをまとめる他のパターンと演算子がたくさんあります。

Flux または Monoでは、Spring MVCまたはSpring WebFluxコントローラーをブロックする必要はありません。コントローラーメソッドから結果のリアクティブ型を返すだけです。同じ原則がKotlinコルーチンとSpring WebFluxにも適用されます。コントローラーメソッドで中断機能を使用するか、Flow を返すだけです。

2.7. テスト

WebClientを使用するコードをテストするには、OkHttp MockWebServer(GitHub) などのモックWebサーバーを使用できます。使用例については、Spring Frameworkテストスイートの WebClientIntegrationTests (GitHub) またはOkHttpリポジトリの static-server (GitHub) サンプルを参照してください。

3. WebSockets

リファレンスドキュメントのこの部分では、リアクティブスタックWebSocketメッセージングのサポートについて説明します。

3.1. WebSocketの概要

WebSocketプロトコルであるRFC 6455(英語) は、単一のTCP接続を介してクライアントとサーバー間に全二重双方向通信チャネルを確立する標準化された方法を提供します。これはHTTPとは異なるTCPプロトコルですが、ポート80および443を使用し、既存のファイアウォールルールの再利用を可能にするHTTP上で動作するように設計されています。

WebSocketの対話は、HTTP Upgrade ヘッダーを使用してアップグレードするか、この場合はWebSocketプロトコルに切り替えるHTTPリクエストで始まります。次の例は、このような相互作用を示しています。

GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket (1)
Connection: Upgrade (2)
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
1 Upgrade ヘッダー。
2 Upgrade 接続を使用します。

通常の200ステータスコードの代わりに、WebSocketをサポートするサーバーは、次のような出力を返します。

HTTP/1.1 101 Switching Protocols (1)
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
1プロトコルスイッチ

ハンドシェイクに成功した後、HTTPアップグレード要求の基になるTCPソケットは、クライアントとサーバーの両方に対して開いたままになり、メッセージの送受信を継続します。

WebSocketsの動作方法の完全な紹介は、このドキュメントの範囲外です。RFC 6455、HTML5のWebSocketの章、またはWebに関する多くの紹介とチュートリアルのいずれかを参照してください。

WebSocketサーバーがWebサーバー(nginxなど)の背後で実行されている場合は、WebSocketアップグレードリクエストをWebSocketサーバーに渡すように構成する必要がある可能性が高いことに注意してください。同様に、アプリケーションがクラウド環境で実行されている場合は、WebSocketサポートに関連するクラウドプロバイダーの指示を確認してください。

3.1.1. HTTP対WebSocket

WebSocketはHTTP互換であるように設計されており、HTTPリクエストで始まりますが、2つのプロトコルが非常に異なるアーキテクチャとアプリケーションプログラミングモデルにつながることを理解することが重要です。

HTTPおよびRESTでは、アプリケーションは多くのURLとしてモデル化されます。アプリケーションと対話するために、クライアントはこれらのURLにアクセスし、リクエスト/レスポンススタイルを使用します。サーバーは、HTTP URL、メソッド、およびヘッダーに基づいて要求を適切なハンドラーにルーティングします。

対照的に、WebSocketsでは、通常、最初の接続用のURLは1つだけです。その後、すべてのアプリケーションメッセージは同じTCP接続で流れます。これは、まったく異なる非同期のイベント駆動型のメッセージングアーキテクチャを指します。

WebSocketは低レベルのトランスポートプロトコルでもあり、HTTPとは異なり、メッセージのコンテンツにセマンティクスを規定していません。つまり、クライアントとサーバーがメッセージのセマンティクスに同意しない限り、メッセージをルーティングまたは処理する方法はありません。

WebSocketクライアントおよびサーバーは、HTTPハンドシェイク要求の Sec-WebSocket-Protocol ヘッダーを介して、より高いレベルのメッセージングプロトコル(たとえば、STOMP)の使用をネゴシエートできます。それがなければ、彼らは彼ら自身のコンベンションを考え出す必要があります。

3.1.2. WebSocketsを使用する場合

WebSocketsは、Webページを動的かつインタラクティブにすることができます。ただし、多くの場合、AjaxとHTTPストリーミングまたはロングポーリングの組み合わせにより、シンプルで効果的なソリューションを提供できます。

例:ニュース、メール、ソーシャルフィードは動的に更新する必要がありますが、数分ごとに更新しても問題ありません。一方、コラボレーション、ゲーム、および金融アプリは、リアルタイムにさらに近づける必要があります。

遅延だけが決定要因ではありません。メッセージの量が比較的少ない場合(ネットワーク障害の監視など)、HTTPストリーミングまたはポーリングは効果的なソリューションを提供できます。WebSocketの使用に最適なのは、低レイテンシー、高周波数、高ボリュームの組み合わせです。

また、インターネット上では、Upgrade ヘッダーを渡すように構成されていないか、アイドル状態であると思われる長寿命の接続を閉じるため、コントロール外の制限プロキシがWebSocket相互作用を妨げる可能性があることに注意してください。これは、ファイアウォール内の内部アプリケーションにWebSocketを使用することは、一般公開アプリケーションよりも簡単な決定であることを意味します。

3.2. WebSocket API

Spring Frameworkは、WebSocketメッセージを処理するクライアント側およびサーバー側のアプリケーションを作成するために使用できるWebSocket APIを提供します。

3.2.1. サーバー

WebSocketサーバーを作成するには、最初に WebSocketHandlerを作成できます。次の例は、その方法を示しています。

Java
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;

public class MyWebSocketHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        // ...
    }
}
Kotlin
import org.springframework.web.reactive.socket.WebSocketHandler
import org.springframework.web.reactive.socket.WebSocketSession

class MyWebSocketHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {
        // ...
    }
}

次に、次の例に示すように、URLにマップして WebSocketHandlerAdapterを追加できます。

Java
@Configuration
class WebConfig {

    @Bean
    public HandlerMapping handlerMapping() {
        Map<String, WebSocketHandler> map = new HashMap<>();
        map.put("/path", new MyWebSocketHandler());
        int order = -1; // before annotated controllers

        return new SimpleUrlHandlerMapping(map, order);
    }

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter();
    }
}
Kotlin
@Configuration
class WebConfig {

    @Bean
    fun handlerMapping(): HandlerMapping {
        val map = mapOf("/path" to MyWebSocketHandler())
        val order = -1 // before annotated controllers

        return SimpleUrlHandlerMapping(map, order)
    }

    @Bean
    fun handlerAdapter() =  WebSocketHandlerAdapter()
}

3.2.2. WebSocketHandler

WebSocketHandlerhandle メソッドは、WebSocketSession を取得して Mono<Void> を返し、セッションのアプリケーション処理が完了したことを示します。セッションは、受信メッセージ用と送信メッセージ用の2つのストリームを介して処理されます。次の表に、ストリームを処理する2つのメソッドを示します。

WebSocketSession説明

Flux<WebSocketMessage> receive()

受信メッセージストリームへのアクセスを提供し、接続が閉じられると完了します。

Mono<Void> send(Publisher<WebSocketMessage>)

送信メッセージのソースを取得し、メッセージを書き込み、ソースが完了して書き込みが完了すると完了する Mono<Void> を返します。

WebSocketHandler は、受信ストリームと送信ストリームを統合されたフローに構成し、そのフローの補完を反映する Mono<Void> を返す必要があります。アプリケーション要件に応じて、統合フローは次の場合に完了します。

  • 受信または送信のメッセージストリームが完了します。

  • 受信ストリームは完了します(つまり、接続が閉じられます)が、送信ストリームは無限です。

  • WebSocketSessionclose メソッドを介して選択したポイント。

受信および送信のメッセージストリームが一緒に構成されている場合、Reactive Streamsシグナルはアクティビティを終了するため、接続が開いているかどうかを確認する必要はありません。受信ストリームは完了またはエラー信号を受信し、送信ストリームはキャンセル信号を受信します。

ハンドラーの最も基本的な実装は、受信ストリームを処理するものです。次の例は、このような実装を示しています。

Java
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.receive()            (1)
                .doOnNext(message -> {
                    // ...                  (2)
                })
                .concatMap(message -> {
                    // ...                  (3)
                })
                .then();                    (4)
    }
}
1受信メッセージのストリームにアクセスします。
2各メッセージで何かをします。
3メッセージコンテンツを使用するネストされた非同期操作を実行します。
4受信が完了したときに完了する Mono<Void> を返します。
Kotlin
class ExampleHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {
        return session.receive()            (1)
                .doOnNext {
                    // ...                  (2)
                }
                .concatMap {
                    // ...                  (3)
                }
                .then()                     (4)
    }
}
1受信メッセージのストリームにアクセスします。
2各メッセージで何かをします。
3メッセージコンテンツを使用するネストされた非同期操作を実行します。
4受信が完了したときに完了する Mono<Void> を返します。
ネストされた非同期操作の場合、プールされたデータバッファーを使用する基になるサーバー(たとえば、Netty)で message.retain() を呼び出す必要があります。そうしないと、データを読み取る前にデータバッファが解放される可能性があります。背景については、データバッファとコーデックを参照してください。

次の実装では、受信ストリームと送信ストリームを組み合わせます。

Java
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Flux<WebSocketMessage> output = session.receive()               (1)
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .map(value -> session.textMessage("Echo " + value));    (2)

        return session.send(output);                                    (3)
    }
}
1受信メッセージストリームを処理します。
2送信メッセージを作成し、結合されたフローを作成します。
3受信を続けている間に完了しない Mono<Void> を返します。
Kotlin
class ExampleHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {

        val output = session.receive()                      (1)
                .doOnNext {
                    // ...
                }
                .concatMap {
                    // ...
                }
                .map { session.textMessage("Echo $it") }    (2)

        return session.send(output)                         (3)
    }
}
1受信メッセージストリームを処理します。
2送信メッセージを作成し、結合されたフローを作成します。
3受信を続けている間に完了しない Mono<Void> を返します。

次の例に示すように、受信ストリームと送信ストリームは独立しており、完了のためにのみ結合できます。

Java
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Mono<Void> input = session.receive()                                (1)
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .then();

        Flux<String> source = ... ;
        Mono<Void> output = session.send(source.map(session::textMessage)); (2)

        return Mono.zip(input, output).then();                              (3)
    }
}
1受信メッセージストリームを処理します。
2送信メッセージを送信します。
3ストリームに参加し、いずれかのストリームが終了すると完了する Mono<Void> を返します。
Kotlin
class ExampleHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {

        val input = session.receive()                                   (1)
                .doOnNext {
                    // ...
                }
                .concatMap {
                    // ...
                }
                .then()

        val source: Flux<String> = ...
        val output = session.send(source.map(session::textMessage))     (2)

        return Mono.zip(input, output).then()                           (3)
    }
}
1受信メッセージストリームを処理します。
2送信メッセージを送信します。
3ストリームに参加し、いずれかのストリームが終了すると完了する Mono<Void> を返します。

3.2.3. DataBuffer

DataBuffer は、WebFluxのバイトバッファーの表現です。参照のSpringコア部分は、データバッファとコーデックのセクションでさらに詳しく説明しています。理解すべき重要な点は、Nettyのような一部のサーバーでは、バイトバッファーがプールされ、参照カウントされ、メモリリークを避けるために消費されたときに解放する必要があるということです。

Nettyで実行する場合、アプリケーションが解放されないように入力データバッファーを保持する場合は、DataBufferUtils.retain(dataBuffer) を使用し、その後バッファーが消費されるときに DataBufferUtils.release(dataBuffer) を使用する必要があります。

3.2.4. ハンドシェーク

WebSocketHandlerAdapterWebSocketServiceに委譲します。デフォルトでは、これは HandshakeWebSocketServiceのインスタンスであり、WebSocket要求で基本的なチェックを実行し、使用中のサーバーに RequestUpgradeStrategy を使用します。現在、Reactor、Netty、Tomcat、Jetty、およびUndertowの組み込みサポートがあります。

HandshakeWebSocketServicePredicate<String> を設定して WebSession から属性を抽出し、WebSocketSessionの属性に挿入できる sessionAttributePredicate プロパティを公開します。

3.2.5. サーバー構成

各サーバーの RequestUpgradeStrategy は、基盤となるWebSocketエンジンで使用可能なWebSocket関連の構成オプションを公開します。次の例では、Tomcatで実行するときにWebSocketオプションを設定します。

Java
@Configuration
class WebConfig {

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter(webSocketService());
    }

    @Bean
    public WebSocketService webSocketService() {
        TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
        strategy.setMaxSessionIdleTimeout(0L);
        return new HandshakeWebSocketService(strategy);
    }
}
Kotlin
@Configuration
class WebConfig {

    @Bean
    fun handlerAdapter() =
            WebSocketHandlerAdapter(webSocketService())

    @Bean
    fun webSocketService(): WebSocketService {
        val strategy = TomcatRequestUpgradeStrategy().apply {
            setMaxSessionIdleTimeout(0L)
        }
        return HandshakeWebSocketService(strategy)
    }
}

サーバーのアップグレード戦略を確認して、使用可能なオプションを確認してください。現在、TomcatとJettyのみがこのようなオプションを公開しています。

3.2.6. CORS

CORSを設定し、WebSocketエンドポイントへのアクセスを制限する最も簡単な方法は、WebSocketHandlerCorsConfigurationSource を実装させ、許可された発信元、ヘッダー、その他の詳細を含む CorsConfiguraiton を返すことです。それができない場合は、SimpleUrlHandlercorsConfigurations プロパティを設定して、URLパターンでCORS設定を指定することもできます。両方が指定されている場合は、CorsConfigurationcombine メソッドを使用して結合されます。

3.2.7. クライアント

Spring WebFluxは、Reactor、Netty、Tomcat、Jetty、Undertow、および標準Java(つまりJSR-356)の実装で WebSocketClient 抽象化を提供します。

Tomcatクライアントは事実上、WebSocketSession 処理にいくつかの追加機能を備えた標準Javaの拡張であり、Tomcat固有のAPIを利用してバックプレッシャーのメッセージの受信を一時停止します。

WebSocketセッションを開始するには、クライアントのインスタンスを作成し、その execute メソッドを使用できます。

Java
WebSocketClient client = new ReactorNettyWebSocketClient();

URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
        session.receive()
                .doOnNext(System.out::println)
                .then());
Kotlin
val client = ReactorNettyWebSocketClient()

        val url = URI("ws://localhost:8080/path")
        client.execute(url) { session ->
            session.receive()
                    .doOnNext(::println)
            .then()
        }

Jettyなどの一部のクライアントは Lifecycle を実装しており、使用する前に停止および開始する必要があります。すべてのクライアントには、基になるWebSocketクライアントの構成に関連するコンストラクターオプションがあります。

4. テスト

spring-test モジュールは、ServerHttpRequest, ServerHttpResponseおよび ServerWebExchangeのモック実装を提供します。モックオブジェクトの説明については、Spring Webリアクティブを参照してください。

WebTestClient は、これらのモック要求および応答オブジェクトに基づいて構築され、HTTPサーバーなしでWebFluxアプリケーションのテストをサポートします。エンドツーエンドの統合テストにも WebTestClient を使用できます。

5. RSocket

このセクションでは、Spring FrameworkのRSocketプロトコルのサポートについて説明します。

5.1. 概要

RSocketは、TCP、WebSocket、およびその他のバイトストリームトランスポートを介した多重化された二重通信用のアプリケーションプロトコルであり、次の相互作用モデルのいずれかを使用します。

  • Request-Response  — send one message and receive one back.

  • Request-Stream  — send one message and receive a stream of messages back.

  • Channel  — send streams of messages in both directions.

  • Fire-and-Forget  — send a one-way message.

最初の接続が確立されると、両側が対称になり、各側が上記の相互作用のいずれかを開始できるため、「クライアント」と「サーバー」の区別が失われます。これが、プロトコルで参加側を「リクエスター」および「レスポンダー」と呼び、上記の相互作用を「リクエストストリーム」または単に「リクエスト」と呼ぶ理由です。

RSocketプロトコルの主な機能と利点は次のとおりです。

  • リアクティブストリーム(英語) semantics across network boundary — for streaming requests such as Request-Stream and Channel , back pressure signals travel between requester and responder, allowing a requester to slow down a responder at the source, hence reducing reliance on network layer congestion control, and the need for buffering at the network level or at any level.

  • Request throttling — this feature is named "Leasing" after the LEASE frame that can be sent from each end to limit the total number of requests allowed by other end for a given time. Leases are renewed periodically.

  • Session resumption — this is designed for loss of connectivity and requires some state to be maintained. The state management is transparent for applications, and works well in combination with back pressure which can stop a producer when possible and reduce the amount of state required.

  • 大きなメッセージの断片化と再組み立て。

  • キープアライブ(ハートビート)。

RSocketは複数の言語で実装(GitHub) されています。Java ライブラリ(GitHub) プロジェクトReactor(英語) およびReactor Netty(GitHub) に基づいてトランスポート用に構築されています。つまり、アプリケーションのReactive Streams Publishersからの信号は、RSocketを介してネットワーク全体に透過的に伝播します。

5.1.1. プロトコル

RSocketの利点の1つはそれがワイヤの上でよく定義された振舞いと何らかのプロトコル拡張(GitHub) に伴う読みやすい仕様(英語) を持っているということです。言語の実装や高レベルのフレームワークAPIに関係なく、仕様を読むことをお勧めします。このセクションでは、コンテキストを確立するための簡潔な概要を提供します。

接続

最初に、クライアントはTCPやWebSocketなどの低レベルのストリーミングトランスポートを介してサーバーに接続し、SETUP フレームをサーバーに送信して接続のパラメーターを設定します。

サーバーは SETUP フレームを拒否する場合がありますが、通常、送信(クライアント用)および受信(サーバー用)した後、SETUP がリースセマンティクスを使用してリクエストの数を制限しない限り、リクエストを開始できます。どちらの側も、要求を許可するために、もう一方の端からの LEASE フレームを待つ必要があります。

リクエストをする

接続が確立されると、両側でフレーム REQUEST_RESPONSE, REQUEST_STREAM, REQUEST_CHANNELまたは REQUEST_FNFのいずれかを介して要求を開始できます。これらの各フレームは、リクエスターからレスポンダーに1つのメッセージを運びます。

次に、レスポンダーは応答メッセージとともに PAYLOAD フレームを返します。REQUEST_CHANNEL の場合、リクエスターはさらに多くの要求メッセージを含む PAYLOAD フレームを送信します。

要求に Request-StreamChannelなどのメッセージのストリームが含まれる場合、レスポンダーはリクエスターからの要求信号を考慮する必要があります。需要はメッセージの数として表されます。初期需要は、REQUEST_STREAM および REQUEST_CHANNEL フレームで指定されます。後続の要求は、REQUEST_N フレームを介して通知されます。

各側は、METADATA_PUSH フレームを介して、個々の要求ではなく、接続全体に関するメタデータ通知も送信できます。

メッセージフォーマット

RSocketメッセージにはデータとメタデータが含まれます。メタデータは、ルートやセキュリティトークンなどを送信するために使用できます。データとメタデータは異なる形式にすることができます。それぞれのMIMEタイプは SETUP フレームで宣言され、特定の接続のすべての要求に適用されます。

すべてのメッセージにメタデータを含めることができますが、通常、ルートなどのメタデータはリクエストごとであるため、リクエストの最初のメッセージ、つまりフレーム REQUEST_RESPONSE, REQUEST_STREAM, REQUEST_CHANNELまたは REQUEST_FNFの1つにのみ含まれます。

プロトコル拡張機能は、アプリケーションで使用する一般的なメタデータ形式を定義します。

5.1.2. Java実装

RSocketのJava実装(GitHub) プロジェクトReactor(英語) 上に構築されています。TCPおよびWebSocketのトランスポートはReactor Netty(GitHub) に基づいています。Reactive Streamsライブラリとして、Reactorはプロトコルの実装作業を簡素化します。アプリケーションでは、宣言演算子と透過的な背圧サポートを備えた Flux および Mono を使用するのが自然です。

RSocket JavaのAPIは、意図的に最小限で基本的なものです。プロトコル機能に焦点を当てており、アプリケーションプログラミングモデル(RPC codegen vs otherなど)をより高いレベルの独立した関心事として残しています。

メイン契約io.rsocket.RSocket(GitHub) は、単一のメッセージの約束を表す Mono、メッセージのストリームを表す Flux、およびバイトバッファーとしてデータとメタデータにアクセスする実際のメッセージを表す io.rsocket.Payload を使用して、4つの要求対話タイプをモデル化します。 RSocket 契約は対称的に使用されます。要求の場合、アプリケーションには、要求を実行する RSocket が与えられます。応答のために、アプリケーションは RSocket を実装してリクエストを処理します。

これは、徹底的な紹介を意図したものではありません。ほとんどの場合、SpringアプリケーションはそのAPIを直接使用する必要はありません。ただし、Springに依存しないRSocketを確認または実験することが重要な場合があります。RSocket Javaリポジトリには、APIとプロトコルの機能を示す多数のサンプルアプリ(GitHub) が含まれています。

5.1.3. Spring サポート

spring-messaging モジュールには次のものが含まれます。

spring-web モジュールには、Jackson CBOR / JSONなどの Encoder および Decoder 実装、およびRSocketアプリケーションが必要とする可能性が高いProtobufが含まれています。また、効率的なルートマッチングのためにプラグインできる PathPatternParser も含まれています。

Spring Boot 2.2は、WebFluxサーバーでWebSocketを介してRSocketを公開するオプションを含む、TCPまたはWebSocketを介したRSocketサーバーの立ち上げをサポートします。 RSocketRequester.Builder および RSocketStrategiesのクライアントサポートと自動構成もあります。詳細については、Spring BootリファレンスのRSocketセクションを参照してください。

Spring Security 5.2はRSocketサポートを提供します。

Spring Integration 5.2は、RSocketクライアントおよびサーバーと対話するための受信および送信ゲートウェイを提供します。詳細については、Spring Integrationリファレンスマニュアルを参照してください。

Spring Cloud GatewayはRSocket接続をサポートしています。

5.2. RSocketRequester

RSocketRequester は、RSocket要求を実行するための流れるようなAPIを提供し、低レベルのデータバッファーではなく、データとメタデータのオブジェクトを受け入れて返します。対称的に使用して、クライアントからリクエストを作成したり、サーバーからリクエストを作成したりできます。

5.2.1. クライアントリクエスター

クライアント側で RSocketRequester を取得するには、最初のRSocket SETUP フレームを準備して送信するとともにサーバーに接続する必要があります。 RSocketRequester はそのためのビルダーを提供します。内部的にRSocket Javaの RSocketFactoryを使用します。

これは、デフォルト設定で接続する最も基本的な方法です。

Java
Mono<RSocketRequester> requesterMono = RSocketRequester.builder()
    .connectTcp("localhost", 7000);

Mono<RSocketRequester> requesterMono = RSocketRequester.builder()
    .connectWebSocket(URI.create("https://example.org:8080/rsocket"));
Kotlin
import org.springframework.messaging.rsocket.connectTcpAndAwait
import org.springframework.messaging.rsocket.connectWebSocketAndAwait

val requester = RSocketRequester.builder()
        .connectTcpAndAwait("localhost", 7000)

val requester = RSocketRequester.builder()
        .connectWebSocketAndAwait(URI.create("https://example.org:8080/rsocket"))

上記は延期されます。リクエスターを実際に接続して使用するには:

Java
// Connect asynchronously
RSocketRequester.builder().connectTcp("localhost", 7000)
    .subscribe(requester -> {
        // ...
    });

// Or block
RSocketRequester requester = RSocketRequester.builder()
    .connectTcp("localhost", 7000)
    .block(Duration.ofSeconds(5));
Kotlin
// Connect asynchronously
import org.springframework.messaging.rsocket.connectTcpAndAwait

class MyService {

    private var requester: RSocketRequester? = null

    private suspend fun requester() = requester ?:
        RSocketRequester.builder().connectTcpAndAwait("localhost", 7000).also { requester = it }

    suspend fun doSomething() = requester().route(...)
}

// Or block
import org.springframework.messaging.rsocket.connectTcpAndAwait

class MyService {

    private val requester = runBlocking {
        RSocketRequester.builder().connectTcpAndAwait("localhost", 7000)
    }

    suspend fun doSomething() = requester.route(...)
}
接続設定

RSocketRequester.Builder は、初期 SETUP フレームをカスタマイズするために以下を提供します。

  • dataMimeType(MimeType)  — set the mime type for data on the connection.

  • metadataMimeType(MimeType)  — set the mime type for metadata on the connection.

  • setupData(Object)  — data to include in the SETUP .

  • setupRoute(String, Object…​)  — route in the metadata to include in the SETUP .

  • setupMetadata(Object, MimeType)  — other metadata to include in the SETUP .

データの場合、デフォルトのMIMEタイプは最初に構成された Decoderから派生します。メタデータの場合、デフォルトのMIMEタイプは複合メタデータ(GitHub) であり、リクエストごとに複数のメタデータ値とMIMEタイプのペアを許可します。通常、両方を変更する必要はありません。

SETUP フレームのデータとメタデータはオプションです。サーバー側では、@ConnectMappingメソッドを使用して、接続の開始と SETUP フレームのコンテンツを処理できます。メタデータは、接続レベルのセキュリティに使用できます。

戦略

RSocketRequester.BuilderRSocketStrategies を受け入れて、リクエスターを構成します。これを使用して、データおよびメタデータ値の(de)-serializationのエンコーダーおよびデコーダーを提供する必要があります。デフォルトでは、String, byte[]および ByteBufferspring-core からの基本コーデックのみが登録されます。 spring-web を追加すると、次のように登録できるより多くのものにアクセスできます。

Java
RSocketStrategies strategies = RSocketStrategies.builder()
    .encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
    .decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
    .build();

Mono<RSocketRequester> requesterMono = RSocketRequester.builder()
    .rsocketStrategies(strategies)
    .connectTcp("localhost", 7000);
Kotlin
import org.springframework.messaging.rsocket.connectTcpAndAwait

val strategies = RSocketStrategies.builder()
        .encoders { it.add(Jackson2CborEncoder()) }
        .decoders { it.add(Jackson2CborDecoder()) }
        .build()

val requester = RSocketRequester.builder()
        .rsocketStrategies(strategies)
        .connectTcpAndAwait("localhost", 7000)

RSocketStrategies は再利用のために設計されています。いくつかのシナリオでは、たとえば同じアプリケーションのクライアントとサーバーの場合、Spring構成で宣言することをお勧めします。

クライアントレスポンダー

RSocketRequester.Builder を使用して、サーバーからの要求に対するレスポンダーを構成できます。

サーバーで使用されているものと同じインフラストラクチャに基づいてクライアント側の応答にアノテーション付きハンドラーを使用できますが、次のようにプログラムで登録します。

Java
RSocketStrategies strategies = RSocketStrategies.builder()
    .routeMatcher(new PathPatternRouteMatcher())  (1)
    .build();

ClientHandler handler = new ClientHandler(); (2)

Mono<RSocketRequester> requesterMono = RSocketRequester.builder()
    .rsocketFactory(RSocketMessageHandler.clientResponder(strategies, handler)) (3)
    .connectTcp("localhost", 7000);
1 spring-web が存在する場合は、効率的なルートマッチングのために PathPatternRouteMatcherを使用します。
2 @MessageMaping または @ConnectMapping メソッドを含むレスポンダーを作成します。
3 RSocketMessageHandler の静的ファクトリーメソッドを使用して、1つ以上のレスポンダーを登録します。
Kotlin
import org.springframework.messaging.rsocket.connectTcpAndAwait

val strategies = RSocketStrategies.builder()
        .routeMatcher(PathPatternRouteMatcher())  (1)
        .build()

val handler = ClientHandler() (2)

val requester = RSocketRequester.builder()
        .rsocketFactory(RSocketMessageHandler.clientResponder(strategies, handler)) (3)
        .connectTcpAndAwait("localhost", 7000)
1 spring-web が存在する場合は、効率的なルートマッチングのために PathPatternRouteMatcherを使用します。
2 @MessageMaping または @ConnectMapping メソッドを含むレスポンダーを作成します。
3 RSocketMessageHandler の静的ファクトリーメソッドを使用して、1つ以上のレスポンダーを登録します。

上記は、クライアントレスポンダのプログラムによる登録用に設計されたショートカットにすぎないことに注意してください。クライアントレスポンダーがSpring構成にある代替シナリオの場合、RSocketMessageHandler をSpring Beanとして宣言し、次のように適用できます。

Java
ApplicationContext context = ... ;
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);

Mono<RSocketRequester> requesterMono = RSocketRequester.builder()
    .rsocketFactory(factory -> factory.acceptor(handler.responder()))
    .connectTcp("localhost", 7000);
Kotlin
import org.springframework.beans.factory.getBean
import org.springframework.messaging.rsocket.connectTcpAndAwait

val context: ApplicationContext = ...
val handler = context.getBean<RSocketMessageHandler>()

val requester = RSocketRequester.builder()
        .rsocketFactory { it.acceptor(handler.responder()) }
        .connectTcpAndAwait("localhost", 7000)

上記の場合、RSocketMessageHandlersetHandlerPredicate を使用して、クライアントレスポンダーを検出するための別の戦略に切り替える必要がある場合もあります。 @RSocketClientResponder などのカスタムアノテーションとデフォルトの @Controllerに基づいています。これは、クライアントとサーバー、または同じアプリケーション内の複数のクライアントを使用するシナリオで必要です。

プログラミングモデルの詳細については、アノテーション付きレスポンダーも参照してください。

拡張

RSocketRequesterBuilder は、キープアライブインターバル、セッション再開、インターセプターなどのさらなる設定オプションのために、RSocket Javaから基礎となる ClientRSocketFactory を公開するコールバックを提供します。次のように、そのレベルでオプションを構成できます。

Java
Mono<RSocketRequester> requesterMono = RSocketRequester.builder()
    .rsocketFactory(factory -> {
        // ...
    })
    .connectTcp("localhost", 7000);
Kotlin
import org.springframework.messaging.rsocket.connectTcpAndAwait

val requester = RSocketRequester.builder()
        .rsocketFactory {
            //...
        }.connectTcpAndAwait("localhost", 7000)

5.2.2. サーバーリクエスター

サーバーから接続されたクライアントにリクエストを行うことは、サーバーから接続されたクライアントのリクエスターを取得することです。

アノテーション付きレスポンダーでは、@ConnectMapping および @MessageMapping メソッドは RSocketRequester 引数をサポートします。これを使用して、接続のリクエスターにアクセスします。 @ConnectMapping メソッドは本質的に SETUP フレームのハンドラーであり、リクエストを開始する前に処理する必要があることに注意してください。そのため、最初のリクエストは処理から分離する必要があります。例:

Java
@ConnectMapping
Mono<Void> handle(RSocketRequester requester) {
    requester.route("status").data("5")
        .retrieveFlux(StatusReport.class)
        .subscribe(bar -> { (1)
            // ...
        });
    return ... (2)
}
1処理とは無関係に、要求を非同期的に開始します。
2処理を実行し、完了 Mono<Void>を返します。
Kotlin
@ConnectMapping
suspend fun handle(requester: RSocketRequester) {
    GlobalScope.launch {
        requester.route("status").data("5").retrieveFlow<StatusReport>().collect { (1)
            // ...
        }
    }
    /// ... (2)
}
1処理とは無関係に、要求を非同期的に開始します。
2サスペンド機能で処理してください。

5.2.3. 要求

クライアントまたはサーバーのリクエスターを取得したら、次のようにリクエストを作成できます。

Java
ViewBox viewBox = ... ;

Flux<AirportLocation> locations = requester.route("locate.radars.within") (1)
        .data(viewBox) (2)
        .retrieveFlux(AirportLocation.class); (3)
1要求メッセージのメタデータに含めるルートを指定します。
2要求メッセージのデータを提供します。
3予想される応答を宣言します。
Kotlin
val viewBox: ViewBox = ...

val locations = requester.route("locate.radars.within") (1)
        .data(viewBox) (2)
        .retrieveFlow<AirportLocation>() (3)
1要求メッセージのメタデータに含めるルートを指定します。
2要求メッセージのデータを提供します。
3予想される応答を宣言します。

相互作用タイプは、入力と出力のカーディナリティから暗黙的に決定されます。上記の例は、1つの値が送信され、値のストリームが受信されるため、Request-Stream です。ほとんどの場合、入力と出力の選択がRSocketインタラクションの種類と、レスポンダーが期待する入力と出力の種類と一致する限り、これについて考える必要はありません。無効な組み合わせの唯一の例は、多対1です。

data(Object) メソッドは、Flux および Monoを含むすべてのリアクティブストリーム Publisher、および ReactiveAdapterRegistryに登録されている値の他のプロデューサーも受け入れます。同じタイプの値を生成する Flux などの複数値 Publisher の場合、すべての要素でタイプチェックと Encoder ルックアップを避けるために、オーバーロードされた data メソッドのいずれかを使用することを検討してください。

data(Object producer, Class<?> elementClass);
data(Object producer, ParameterizedTypeReference<?> elementTypeRef);

data(Object) ステップはオプションです。データを送信しないリクエストの場合はスキップします。

Java
Mono<AirportLocation> location = requester.route("find.radar.EWR"))
    .retrieveMono(AirportLocation.class);
Kotlin
import org.springframework.messaging.rsocket.retrieveAndAwait

val location = requester.route("find.radar.EWR")
    .retrieveAndAwait<AirportLocation>()

複合メタデータ(GitHub) (デフォルト)を使用し、値が登録済み Encoderでサポートされている場合、追加のメタデータ値を追加できます。例:

Java
String securityToken = ... ;
ViewBox viewBox = ... ;
MimeType mimeType = MimeType.valueOf("message/x.rsocket.authentication.bearer.v0");

Flux<AirportLocation> locations = requester.route("locate.radars.within")
        .metadata(securityToken, mimeType)
        .data(viewBox)
        .retrieveFlux(AirportLocation.class);
Kotlin
import org.springframework.messaging.rsocket.retrieveFlow

val requester: RSocketRequester = ...

val securityToken: String = ...
val viewBox: ViewBox = ...
val mimeType = MimeType.valueOf("message/x.rsocket.authentication.bearer.v0")

val locations = requester.route("locate.radars.within")
        .metadata(securityToken, mimeType)
        .data(viewBox)
        .retrieveFlow<AirportLocation>()

Fire-and-Forget の場合、Mono<Void>を返す send() メソッドを使用します。 Mono は、メッセージが正常に送信されたことのみを示し、処理されたことを示すものではないことに注意してください。

5.3. アノテーション付きレスポンダー

RSocketレスポンダーは、@MessageMapping および @ConnectMapping メソッドとして実装できます。 @MessageMapping メソッドは個々のリクエストを処理し、@ConnectMapping メソッドは接続レベルのイベント(セットアップとメタデータプッシュ)を処理します。アノテーション付きのレスポンダーは、サーバー側からの応答とクライアント側からの応答のために対称的にサポートされます。

5.3.1. サーバーレスポンダー

サーバー側でアノテーション付きのレスポンダーを使用するには、RSocketMessageHandler をSpring構成に追加して、@MessageMapping および @ConnectMapping メソッドで @Controller Beanを検出します。

Java
@Configuration
static class ServerConfig {

    @Bean
    public RSocketMessageHandler rsocketMessageHandler() {
        RSocketMessageHandler handler = new RSocketMessageHandler();
        handler.routeMatcher(new PathPatternRouteMatcher());
        return handler;
    }
}
Kotlin
@Configuration
class ServerConfig {

    @Bean
    fun rsocketMessageHandler() = RSocketMessageHandler().apply {
        routeMatcher = PathPatternRouteMatcher()
    }
}

次に、Java RSocket APIを使用してRSocketサーバーを起動し、レスポンダーの RSocketMessageHandler を次のように接続します。

Java
ApplicationContext context = ... ;
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);

CloseableChannel server =
    RSocketFactory.receive()
        .acceptor(handler.responder())
        .transport(TcpServerTransport.create("localhost", 7000))
        .start()
        .block();
Kotlin
import org.springframework.beans.factory.getBean

val context: ApplicationContext = ...
val handler = context.getBean<RSocketMessageHandler>()

val server = RSocketFactory.receive()
        .acceptor(handler.responder())
        .transport(TcpServerTransport.create("localhost", 7000))
        .start().awaitFirst()

RSocketMessageHandler は、デフォルトで複合(GitHub) メタデータとルーティング(GitHub) メタデータをサポートしています。別のMIMEタイプに切り替えるか、追加のメタデータMIMEタイプを登録する必要がある場合は、MetadataExtractorを設定できます。

サポートするメタデータおよびデータ形式に必要な Encoder および Decoder インスタンスを設定する必要があります。コーデックの実装には、spring-web モジュールが必要になる可能性があります。

デフォルトでは、SimpleRouteMatcherAntPathMatcherを介したルートのマッチングに使用されます。効率的なルートマッチングのために、spring-web から PathPatternRouteMatcher を差し込むことをお勧めします。RSocketルートは階層化できますが、URLパスではありません。両方のルートマッチャーが「.」を使用するように構成されています。デフォルトではセパレータとして使用され、HTTP URLのようなURLデコードはありません。

RSocketMessageHandler は、同じプロセスでクライアントとサーバー間で構成を共有する必要がある場合に便利な RSocketStrategies を介して構成できます。

Java
@Configuration
static class ServerConfig {

    @Bean
    public RSocketMessageHandler rsocketMessageHandler() {
        RSocketMessageHandler handler = new RSocketMessageHandler();
        handler.setRSocketStrategies(rsocketStrategies());
        return handler;
    }

    @Bean
    public RSocketStrategies rsocketStrategies() {
        return RSocketStrategies.builder()
            .encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
            .decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
            .routeMatcher(new PathPatternRouteMatcher())
            .build();
    }
}
Kotlin
@Configuration
class ServerConfig {

    @Bean
    fun rsocketMessageHandler() = RSocketMessageHandler().apply {
        rSocketStrategies = rsocketStrategies()
    }

    @Bean
    fun rsocketStrategies() = RSocketStrategies.builder()
            .encoders { it.add(Jackson2CborEncoder()) }
            .decoders { it.add(Jackson2CborDecoder()) }
            .routeMatcher(PathPatternRouteMatcher())
            .build()
}

5.3.2. クライアントレスポンダー

クライアント側のアノテーション付き応答者は、RSocketRequester.Builderで構成する必要があります。詳細については、クライアントレスポンダーを参照してください。

5.3.3. @MessageMapping

サーバーまたはクライアントレスポンダーの設定が完了したら、@MessageMapping メソッドを次のように使用できます。

Java
@Controller
public class RadarsController {

    @MessageMapping("locate.radars.within")
    public Flux<AirportLocation> radars(MapRequest request) {
        // ...
    }
}
Kotlin
@Controller
class RadarsController {

    @MessageMapping("locate.radars.within")
    fun radars(request: MapRequest): Flow<AirportLocation> {
        // ...
    }
}

上記の @MessageMapping メソッドは、「locate.radars.within」というルートを持つRequest-Streamインタラクションに応答します。次のメソッド引数を使用するオプションを備えた柔軟なメソッドシグネチャーをサポートしています。

メソッド引数説明

@Payload

リクエストのペイロード。これは、MonoFluxなどの非同期タイプの具体的な値になります。

Note: Use of the annotation is optional. A method argument that is not a simple type and is not any of the other supported arguments, is assumed to be the expected payload.

RSocketRequester

リモートの終了を要求するためのリクエスター。

@DestinationVariable

マッピングパターンの変数に基づいてルートから抽出された値。 @MessageMapping("find.radar.{id}")

@Header

MetadataExtractorで説明されている抽出のために登録されたメタデータ値。

@Headers Map<String, Object>

MetadataExtractorに従って、抽出のために登録されたすべてのメタデータ値。

戻り値は、応答ペイロードとして直列化される1つ以上のオブジェクトであると予想されます。これは、Mono または Fluxのような非同期タイプ、具体的な値、または void または Mono<Void>などの値のない非同期タイプのいずれかです。

@MessageMapping メソッドがサポートするRSocketインタラクションタイプは、入力(つまり、@Payload 引数)と出力のカーディナリティから決定されます。カーディナリティは次のことを意味します。

基数説明

1

明示的な値、または Mono<T>などの単一値の非同期タイプのいずれか。

Flux<T>などの複数値非同期タイプ。

0

入力の場合、これはメソッドに @Payload 引数がないことを意味します。

出力の場合、これは void または Mono<Void>などの値のない非同期タイプです。

以下の表は、すべての入力および出力カーディナリティーの組み合わせと、対応する相互作用タイプを示しています。

入力カーディナリティ出力カーディナリティインタラクションタイプ

0, 1

0

火と忘れ、リクエストとレスポンス

0, 1

1

リクエスト-レスポンス

0, 1

リクエストストリーム

0, 1、多数

リクエストチャネル

5.3.4. @ConnectMapping

@ConnectMapping はRSocket接続の開始時に SETUP フレームを処理し、後続のメタデータは METADATA_PUSH フレーム、つまり io.rsocket.RSocketmetadataPush(Payload) を介して通知をプッシュします。

@ConnectMapping メソッドは、@MessageMappingと同じ引数をサポートしていますが、SETUP および METADATA_PUSH フレームからのメタデータとデータに基づいています。 @ConnectMapping には、メタデータにルートを持つ特定の接続に処理を絞り込むパターンを設定できます。パターンが宣言されていない場合は、すべての接続が一致します。

@ConnectMapping メソッドはデータを返すことができず、戻り値として void または Mono<Void> を使用して宣言する必要があります。新しい接続に対して処理がエラーを返した場合、接続は拒否されます。接続のために RSocketRequester に要求を行うために、処理を遅らせてはなりません。詳細については、サーバーリクエスターを参照してください。

5.4. MetadataExtractor

応答者はメタデータを解釈する必要があります。複合メタデータ(GitHub) では、それぞれ独自のMIMEタイプを使用して、個別にフォーマットされたメタデータ値(ルーティング、セキュリティ、トレースなど)を使用できます。アプリケーションには、サポートするメタデータMIMEタイプを構成する方法と、抽出された値にアクセスする方法が必要です。

MetadataExtractor は、直列化されたメタデータを取得し、デコードされた名前と値のペアを返す契約です。名前付きのヘッダーのように、たとえばアノテーション付きハンドラーメソッドの @Header を介してアクセスできます。

DefaultMetadataExtractorDecoder インスタンスを与えて、メタデータをデコードできます。すぐに使用できる「message / x.rsocket.routing.v0」(GitHub) の組み込みサポートがあり、String にデコードして「ルート」キーに保存します。その他のMIMEタイプの場合は、Decoder を提供して、MIMEタイプを次のように登録する必要があります。

Java
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders);
extractor.metadataToExtract(fooMimeType, Foo.class, "foo");
Kotlin
import org.springframework.messaging.rsocket.metadataToExtract

val extractor = DefaultMetadataExtractor(metadataDecoders)
extractor.metadataToExtract<Foo>(fooMimeType, "foo")

複合メタデータは、独立したメタデータ値を組み合わせるのに適しています。ただし、リクエスターは複合メタデータをサポートしていないか、使用しないことを選択する場合があります。このため、DefaultMetadataExtractor は、デコードされた値を出力マップにマップするカスタムロジックを必要とする場合があります。JSONがメタデータに使用される例を次に示します。

Java
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders);
extractor.metadataToExtract(
    MimeType.valueOf("application/vnd.myapp.metadata+json"),
    new ParameterizedTypeReference<Map<String,String>>() {},
    (jsonMap, outputMap) -> {
        outputMap.putAll(jsonMap);
    });
Kotlin
import org.springframework.messaging.rsocket.metadataToExtract

val extractor = DefaultMetadataExtractor(metadataDecoders)
extractor.metadataToExtract<Map<String, String>>(MimeType.valueOf("application/vnd.myapp.metadata+json")) { jsonMap, outputMap ->
    outputMap.putAll(jsonMap)
}

MetadataExtractor から RSocketStrategiesを構成する場合、RSocketStrategies.Builder に構成済みのデコーダーを使用して抽出プログラムを作成させ、コールバックを使用して登録を次のようにカスタマイズできます。

Java
RSocketStrategies strategies = RSocketStrategies.builder()
    .metadataExtractorRegistry(registry -> {
        registry.metadataToExtract(fooMimeType, Foo.class, "foo");
        // ...
    })
    .build();
Kotlin
import org.springframework.messaging.rsocket.metadataToExtract

val strategies = RSocketStrategies.builder()
        .metadataExtractorRegistry { registry: MetadataExtractorRegistry ->
            registry.metadataToExtract<Foo>(fooMimeType, "foo")
            // ...
        }
        .build()

6. リアクティブライブラリ

spring-webfluxreactor-core に依存し、それを内部的に使用して非同期ロジックを構成し、リアクティブストリームのサポートを提供します。一般的に、WebFlux APIは Flux または Mono を返し(これらは内部で使用されるため)、Reactive Streams Publisher 実装を入力として寛容に受け入れます。 FluxMono の使用は、カーディナリティーの表現に役立つため重要です。たとえば、単一または複数の非同期値が予想されるかどうか、および決定(たとえば、HTTPメッセージのエンコードまたはデコード時)に不可欠です。

アノテーション付きコントローラーの場合、WebFluxはアプリケーションが選択したリアクティブライブラリに透過的に適応します。これは、 ReactiveAdapterRegistry (Javadoc) の助けを借りて行われます。 ReactiveAdapterRegistry (Javadoc) は、リアクティブライブラリおよびその他の非同期タイプのプラグ可能なサポートを提供します。レジストリにはRxJavaおよび CompletableFutureのサポートが組み込まれていますが、他のユーザーも登録できます。

For functional APIs (such as 機能エンドポイント , the WebClient , and others), the general rules for WebFlux APIs apply —  Flux and Mono as return values and a Reactive Streams Publisher as input. When a Publisher , whether custom or from another reactive library, is provided, it can be treated only as a stream with unknown semantics (0..N). If, however, the semantics are known, you can wrap it with Flux or Mono.from(Publisher) instead of passing the raw Publisher .

例: Monoではない Publisher を指定すると、Jackson JSONメッセージライターは複数の値を予期します。メディアタイプが無限ストリーム( application/json+streamなど)を意味する場合、値は個別に書き込まれ、フラッシュされます。それ以外の場合、値はリストにバッファリングされ、JSON配列としてレンダリングされます。

Unofficial Translation by spring.pleiades.io. See the original content.