Spring Web Reactive | 1. Spring WebFlux | 1.4. アノテーション付きコントローラー #1

Web MVC

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

Web MVC

標準の 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. リクエストマッピング(Request Mapping)

Web MVC

@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 パターン

Web MVC

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

パターン 説明 サンプル
? 1 文字に一致 "/pages/t?st.html" matches "/pages/test.html" and "/pages/t3st.html"
* パスセグメント内の 0 個以上の文字に一致します "/resources/*.png" matches "/resources/file.png"
"/projects/*/versions" matches "/projects/spring/versions" but does not match "/projects/spring/boot/versions"
** パスの終わりまで 0 個以上のパスセグメントに一致します "/resources/**" matches "/resources/file.png" and "/resources/images/file.png"
"/resources/**/file.png" is invalid as ** is only allowed at the end of the path.
{name} パスセグメントを照合し、“name” という名前の変数としてキャプチャーします "/projects/{project}/versions" matches "/projects/spring/versions" and captures project=spring
{name:[a-z]+} 正規表現 "[a-z]+" を “name” という名前のパス変数として一致させます "/projects/{project:[a-z]+}/versions" matches "/projects/spring/versions" but not "/projects/spring1/versions"
{*path} パスの最後まで 0 個以上のパスセグメントに一致し、“path” という名前の変数としてキャプチャーします "/resources/{*file}" matches "/resources/images/file.png" and captures file=images/file.png

次の例に示すように、キャプチャーされた 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 が発生します。単純型(intlongDate など)はデフォルトでサポートされており、他のデータ型のサポートを登録できます。型変換および DataBinder を参照してください。

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

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

構文 {varName:regex} は、構文 {varName:regex} を持つ正規表現で URI 変数を宣言します。例: /spring-web-3.0.5 .jar の URL を指定すると、次のメソッドは名前、バージョン、ファイル拡張子を抽出します。

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 はサフィックスパターンマッチングをサポートしていません — Spring MVC とは異なり、/person などのマッピングは /person.* にも一致します。URL ベースのコンテンツネゴシエーションでは、必要に応じて、クエリパラメーターを使用することをお勧めします。クエリパラメーターは、より単純で、より明示的で、URL パスベースのエクスプロイトに対して脆弱ではありません。

パターン比較(Pattern Comparison)

Web MVC

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

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

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

消費可能なメディア型(Consumable Media Types)

Web MVC

次の例に示すように、リクエストの 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) {
    // ...
}

消費属性は否定表現もサポートしています。たとえば、!text/plaintext/plain 以外のコンテンツ型を意味します。

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

生産可能なメディア型(Producible Media Types)

Web MVC

次の例に示すように、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 属性は、クラスレベルの宣言を継承するのではなくオーバーライドします。

パラメーターとヘッダー

Web MVC

クエリパラメーターの条件に基づいてリクエストマッピングを絞り込むことができます。クエリパラメーターの存在(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

Web MVC

@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 にマップできますが、これは一般的なケースでは必要ありません。

カスタムアノテーション

Web MVC

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

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

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

明示的な登録

Web MVC

ハンドラーメソッドをプログラムで登録できます。これは、動的な登録や、異なる 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. 処理メソッド

Web MVC

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

メソッド引数

Web MVC

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

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

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

コントローラーメソッドの引数 説明
ServerWebExchange 完全な ServerWebExchange — HTTP リクエストおよびレスポンス、リクエストおよびセッション属性、checkNotModified メソッドなどのコンテナー。
ServerHttpRequest, ServerHttpResponse HTTP リクエストまたはレスポンスへのアクセス。
WebSession セッションへのアクセス。これは、属性が追加されない限り、新しいセッションの開始を強制しません。リアクティブ型をサポートします。
java.security.Principal 現在認証されているユーザー — 既知の場合、特定の Principal 実装クラスである可能性があります。リアクティブ型をサポートします。
org.springframework.http.HttpMethod リクエストの HTTP メソッド。
java.util.Locale 利用可能な最も具体的な LocaleResolver によって決定される現在のリクエストロケール— 実際には、構成された LocaleResolver/LocaleContextResolver
java.util.TimeZone + java.time.ZoneId LocaleContextResolver によって決定される、現在のリクエストに関連付けられたタイムゾーン。
@PathVariable URI テンプレート変数へのアクセス用。URI パターンを参照してください。
@MatrixVariable URI パスセグメントの名前と値のペアへのアクセス用。マトリックス変数を参照してください。
@RequestParam サーブレットリクエストパラメーターへのアクセス用。パラメーター値は、宣言されたメソッド引数型に変換されます。@RequestParam を参照してください。
@RequestParam の使用はオプションであることに注意してください。たとえば、その属性を設定するためです。この表の後の「その他の引数」を参照してください。
@RequestHeader リクエストヘッダーへのアクセス用。ヘッダー値は、宣言されたメソッド引数型に変換されます。@RequestHeader を参照してください。
@CookieValue クッキーへのアクセス用。Cookie 値は、宣言されたメソッド引数型に変換されます。@CookieValue を参照してください。
@RequestBody HTTP リクエスト本文へのアクセス用。本文コンテンツは、HttpMessageReader インスタンスを使用して、宣言されたメソッド引数型に変換されます。リアクティブ型をサポートします。@RequestBody を参照してください。
HttpEntity<B> リクエストヘッダーと本文へのアクセス用。本体は HttpMessageReader インスタンスで変換されます。リアクティブ型をサポートします。HttpEntity を参照してください。
@RequestPart multipart/form-data リクエストの一部へのアクセス用。リアクティブ型をサポートします。マルチパートコンテンツおよびマルチパートデータを参照してください。
java.util.Map, org.springframework.ui.Model, and org.springframework.ui.ModelMap. For access to the model that is used in HTML controllers and is exposed to templates as part of view rendering.
@ModelAttribute データバインディングと検証が適用されたモデル内の既存の属性(存在しない場合はインスタンス化)へのアクセス用。@ModelAttribute および Model および DataBinder を参照してください。
@ModelAttribute の使用はオプションであることに注意してください。たとえば、その属性を設定するためです。この表の後の「その他の引数」を参照してください。
Errors, BindingResult コマンドオブジェクト、つまり @ModelAttribute 引数の検証およびデータバインディングからのエラーへのアクセス用。Errors または BindingResult 引数は、検証されたメソッド引数の直後に宣言する必要があります。
SessionStatus + class-level @SessionAttributes フォーム処理の完了をマークするために、クラスレベルの @SessionAttributes アノテーションを介して宣言されたセッション属性のクリーンアップをトリガーします。詳細については、@SessionAttributes を参照してください。
UriComponentsBuilder 現在のリクエストのホスト、ポート、スキーム、コンテキストパスに関連する URL を準備するため。URI リンクを参照してください。
@SessionAttribute 任意のセッション属性へのアクセス— クラスレベルの @SessionAttributes 宣言の結果としてセッションに保存されたモデル属性とは対照的。詳細については、@SessionAttribute を参照してください。
@RequestAttribute リクエスト属性へのアクセス用。詳細については、@RequestAttribute を参照してください。
その他の引数 メソッドの引数が上記のいずれにも一致しない場合、デフォルトでは、BeanUtils#isSimpleProperty (Javadoc) によって決定される単純型の場合は @RequestParam として、そうでない場合は @ModelAttribute として解決されます。

戻り値

Web MVC

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

コントローラーメソッドの戻り値 説明
@ResponseBody 戻り値は、HttpMessageWriter インスタンスを介してエンコードされ、レスポンスに書き込まれます。@ResponseBody を参照してください。
HttpEntity<B>, ResponseEntity<B> 戻り値は、HTTP ヘッダーを含む完全なレスポンスを指定し、本文は HttpMessageWriter インスタンスを介してエンコードされ、レスポンスに書き込まれます。ResponseEntity を参照してください。
HttpHeaders ヘッダーを含み、本文を含まないレスポンスを返すため。
String ViewResolver インスタンスで解決され、暗黙的なモデルと一緒に使用されるビュー名 — コマンドオブジェクトと @ModelAttribute メソッドによって決定されます。ハンドラーメソッドは、Model 引数(前述)を宣言することにより、プログラムでモデルを強化することもできます。
View 暗黙的なモデルと一緒にレンダリングするために使用する View インスタンス— コマンドオブジェクトと @ModelAttribute メソッドによって決定されます。ハンドラーメソッドは、Model 引数(前述)を宣言することにより、プログラムでモデルを強化することもできます。
java.util.Map, org.springframework.ui.Model 暗黙的なモデルに追加される属性。ビュー名はリクエストパスに基づいて暗黙的に決定されます。
@ModelAttribute モデルに追加される属性。ビュー名はリクエストパスに基づいて暗黙的に決定されます。
@ModelAttribute はオプションです。この表の後の「その他の戻り値」を参照してください。
Rendering モデルおよびビューのレンダリングシナリオ用の API。
void void、場合によっては非同期(Mono<Void> など)の戻り値の型(または null の戻り値)を持つメソッドは、ServerHttpResponseServerWebExchange 引数、@ResponseStatus アノテーションもある場合、レスポンスを完全に処理したと見なされます。コントローラーが正の ETag または lastModified タイムスタンプチェックを行った場合も同様です。詳細についてはコントローラーを参照してください。
上記のいずれにも当てはまらない場合、void 戻り値の型は、REST コントローラーの「レスポンス本文なし」または HTML コントローラーのデフォルトのビュー名選択を示すこともできます。
Flux<ServerSentEvent>, Observable<ServerSentEvent>, or other reactive type Emit server-sent events. The ServerSentEvent wrapper can be omitted when only data needs to be written (however, text/event-stream must be requested or declared in the mapping through the produces attribute).
Any other return value If a return value is not matched to any of the above, it is, by default, treated as a view name, if it is String or void (default view name selection applies), or as a model attribute to be added to the model, unless it is a simple type, as determined by BeanUtils#isSimpleProperty (Javadoc), in which case it remains unresolved.

Type Conversion

Web MVC

Some annotated controller method arguments that represent String-based request input (for example, @RequestParam, @RequestHeader, @PathVariable, @MatrixVariable, and @CookieValue) can require type conversion if the argument is declared as something other than String.

このような場合、構成済みのコンバーターに基づいて型変換が自動的に適用されます。デフォルトでは、intlongDate などの単純型がサポートされます。型変換は、WebDataBinderDataBinder を参照)を通じてカスタマイズするか、FormattingConversionServiceFormatters を登録してカスタマイズできます(Spring フィールドフォーマットを参照)。

A practical issue in type conversion is the treatment of an empty String source value. Such a value is treated as missing if it becomes null as a result of type conversion. This can be the case for Long, UUID, and other target types. If you want to allow null to be injected, either use the required flag on the argument annotation, or declare the argument as @Nullable.

Matrix Variables

Web MVC

RFC 3986 [IETF] (英語) discusses name-value pairs in path segments. In Spring WebFlux, we refer to those as “matrix variables” based on an “old post” [W3C] (英語) by Tim Berners-Lee, but they can be also be referred to as URI path parameters.

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".

Unlike Spring MVC, in WebFlux, the presence or absence of matrix variables in a URL does not affect request mappings. In other words, you are not required to use a URI variable to mask variable content. That said, if you want to access matrix variables from a controller method, you need to add a URI variable to the path segment where matrix variables are expected. The following example shows how to do so:

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
}

Given that all path segments can contain matrix variables, you may sometimes need to disambiguate which path variable the matrix variable is expected to be in, as the following example shows:

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
}

You can define a matrix variable may be defined as optional and specify a default value as the following example shows:

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
}

To get all matrix variables, use a MultiValueMap, as the following example shows:

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

Web MVC

You can use the @RequestParam annotation to bind query parameters to a method argument in a controller. The following code snippet shows the usage:

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) Using @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) Using @RequestParam.

Method parameters that use the @RequestParam annotation are required by default, but you can specify that a method parameter is optional by setting the required flag of a @RequestParam to false or by declaring the argument with a java.util.Optional wrapper.

Type conversion is applied automatically if the target method parameter type is not String. See Type Conversion.

When a @RequestParam annotation is declared on a Map<String, String> or MultiValueMap<String, String> argument, the map is populated with all query parameters.

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

Web MVC

You can use the @RequestHeader annotation to bind a request header to a method argument in a controller.

The following example shows a request with headers:

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

The following example gets the value of the Accept-Encoding and Keep-Alive headers:

Java

@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding,  // (1)
        @RequestHeader("Keep-Alive") long keepAlive) {  // (2)
    //...
}
  • (1) Get the value of the Accept-Encoging header.
  • (2) Get the value of the Keep-Alive header.

Kotlin

@GetMapping("/demo")
fun handle(
        @RequestHeader("Accept-Encoding") encoding: String,  // (1)
        @RequestHeader("Keep-Alive") keepAlive: Long) {  // (2)
    //...
}
  • (1) Get the value of the Accept-Encoging header.
  • (2) Get the value of the Keep-Alive header.

Type conversion is applied automatically if the target method parameter type is not String. See Type Conversion.

When a @RequestHeader annotation is used on a Map<String, String>, MultiValueMap<String, String>, or HttpHeaders argument, the map is populated with all header values.

@CookieValue

Web MVC

You can use the @CookieValue annotation to bind the value of an HTTP cookie to a method argument in a controller.

The following example shows a request with a cookie:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

The following code sample demonstrates how to get the cookie value:

Java

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {  // (1)
    //...
}
  • (1) Get the cookie value.

Kotlin

@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) {  // (1)
    //...
}
  • (1) Get the cookie value.

Type conversion is applied automatically if the target method parameter type is not String. See Type Conversion.

@ModelAttribute

Web MVC

You can use the @ModelAttribute annotation on a method argument to access an attribute from the model or have it instantiated if not present. The model attribute is also overlain with the values of query parameters and form fields whose names match to field names. This is referred to as data binding, and it saves you from having to deal with parsing and converting individual query parameters and form fields. The following example binds an instance of Pet:

Java

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }  // (1)
  • (1) Bind an instance of Pet.

Kotlin

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { }  // (1)
  • (1) Bind an instance of Pet.

The Pet instance in the preceding example is resolved as follows:

  • From the model if already added through Model.
  • From the HTTP session through @SessionAttributes.
  • From the invocation of a default constructor.
  • From the invocation of a “primary constructor” with arguments that match query parameters or form fields. Argument names are determined through JavaBeans @ConstructorProperties or through runtime-retained parameter names in the bytecode.

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

Data binding can result in errors. By default, a WebExchangeBindException is raised, but, to check for such errors in the controller method, you can add a BindingResult argument immediately next to the @ModelAttribute, as the following example shows:

Java

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {  // (1)
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
  • (1) Adding a BindingResult.

Kotlin

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String {  // (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}
  • (1) Adding a BindingResult.

javax.validation.Valid アノテーションまたは Spring の @Validated アノテーションを追加することで、データバインディング後に検証を自動的に適用できます(Bean ValidationSpring validation も参照)。次の例では @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) Using @Valid on a model attribute argument.

Kotlin

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String {  // (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}
  • (1) Using @Valid on a model attribute argument.

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

Web MVC

@SessionAttributes is used to store model attributes in the WebSession between requests. It is a type-level annotation that declares session attributes used by a specific controller. This typically lists the names of model attributes or types of model attributes that should be transparently stored in the session for subsequent requests to access.

Consider the following example:

Java

@Controller
@SessionAttributes("pet")  // (1)
public class EditPetForm {
    // ...
}
  • (1) Using the @SessionAttributes annotation.

Kotlin

@Controller
@SessionAttributes("pet")  // (1)
class EditPetForm {
    // ...
}
  • (1) Using the @SessionAttributes annotation.

On the first request, when a model attribute with the name, pet, is added to the model, it is automatically promoted to and saved in the WebSession. It remains there until another controller method uses a SessionStatus method argument to clear the storage, as the following example shows:

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) Using the @SessionAttributes annotation.
  • (2) Using a SessionStatus variable.

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) Using the @SessionAttributes annotation.
  • (2) Using a SessionStatus variable.

@SessionAttribute

Web MVC

If you need access to pre-existing session attributes that are managed globally (that is, outside the controller — for example, by a filter) and may or may not be present, you can use the @SessionAttribute annotation on a method parameter, as the following example shows:

Java

@GetMapping("/")
public String handle(@SessionAttribute User user) {  // (1)
    // ...
}
  • (1) Using @SessionAttribute.

Kotlin

@GetMapping("/")
fun handle(@SessionAttribute user: User): String {  // (1)
    // ...
}
  • (1) Using @SessionAttribute.

For use cases that require adding or removing session attributes, consider injecting WebSession into the controller method.

For temporary storage of model attributes in the session as part of a controller workflow, consider using SessionAttributes, as described in @SessionAttributes.

@RequestAttribute

Web MVC

Similarly to @SessionAttribute, you can use the @RequestAttribute annotation to access pre-existing request attributes created earlier (for example, by a WebFilter), as the following example shows:

Java

@GetMapping("/")
public String handle(@RequestAttribute Client client) {  // (1)
    // ...
}
  • (1) Using @RequestAttribute.

Kotlin

@GetMapping("/")
fun handle(@RequestAttribute client: Client): String {  // (1)
    // ...
}
  • (1) Using @RequestAttribute.

Multipart Content

Web MVC

As explained in Multipart Data, ServerWebExchange provides access to multipart content. The best way to handle a file upload form (for example, from a browser) in a controller is through data binding to a command object, as the following example shows:

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 {
        // ...
    }

}

You can also submit multipart requests from non-browser clients in a RESTful service scenario. The following example uses a file along with 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 ...

You can access individual parts with @RequestPart, as the following example shows:

Java

@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata,  // (1)
        @RequestPart("file-data") FilePart file) {  // (2)
    // ...
}
  • (1) Using @RequestPart to get the metadata.
  • (2) Using @RequestPart to get the file.

Kotlin

@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata,  // (1)
        @RequestPart("file-data") FilePart file): String {  // (2)
    // ...
}
  • (1) Using @RequestPart to get the metadata.
  • (2) Using @RequestPart to get the file.

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) Using @RequestPart to get the metadata.

Kotlin

@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData): String {  // (1)
    // ...
}
  • (1) Using @RequestPart to get the metadata.

You can use @RequestPart in combination with javax.validation.Valid or Spring’s @Validated annotation, which causes Standard Bean Validation to be applied. Validation errors lead to a WebExchangeBindException that results in a 400 (BAD_REQUEST) response. The exception contains a BindingResult with the error details and can also be handled in the controller method by declaring the argument with an async wrapper and then using error related operators:

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 {
    // ...
}

To access all multipart data as a MultiValueMap, you can use @RequestBody, as the following example shows:

Java

@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) {  // (1)
    // ...
}
  • (1) Using @RequestBody.

Kotlin

@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String {  // (1)
    // ...
}
  • (1) Using @RequestBody.

To access multipart data sequentially, in streaming fashion, you can use @RequestBody with Flux<Part> (or Flow<Part> in Kotlin) instead, as the following example shows:

Java

@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) {  // (1)
    // ...
}
  • (1) Using @RequestBody.

Kotlin

@PostMapping("/")
fun handle(@RequestBody parts: Flow<Part>): String {  // (1)
    // ...
}
  • (1) Using @RequestBody.

@RequestBody

Web MVC

You can use the @RequestBody annotation to have the request body read and deserialized into an Object through an HttpMessageReader. The following example uses a @RequestBody argument:

Java

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

Kotlin

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

Unlike Spring MVC, in WebFlux, the @RequestBody method argument supports reactive types and fully non-blocking reading and (client-to-server) streaming.

Java

@PostMapping("/accounts")
public void handle(@RequestBody Mono<Account> account) {
    // ...
}

Kotlin

@PostMapping("/accounts")
fun handle(@RequestBody accounts: Flow<Account>) {
    // ...
}

You can use the HTTP message codecs option of the WebFlux Config to configure or customize message readers.

You can use @RequestBody in combination with javax.validation.Valid or Spring’s @Validated annotation, which causes Standard Bean Validation to be applied. Validation errors cause a WebExchangeBindException, which results in a 400 (BAD_REQUEST) response. The exception contains a BindingResult with error details and can be handled in the controller method by declaring the argument with an async wrapper and then using error related operators:

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

Web MVC

HttpEntity is more or less identical to using @RequestBody but is based on a container object that exposes request headers and the body. The following example uses an HttpEntity:

Java

@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}

Kotlin

@PostMapping("/accounts")
fun handle(entity: HttpEntity<Account>) {
    // ...
}

@ResponseBody

Web MVC

You can use the @ResponseBody annotation on a method to have the return serialized to the response body through an HttpMessageWriter. The following example shows how to do so:

Java

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}

Kotlin

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

@ResponseBody is also supported at the class level, in which case it is inherited by all controller methods. This is the effect of @RestController, which is nothing more than a meta-annotation marked with @Controller and @ResponseBody.

@ResponseBody supports reactive types, which means you can return Reactor or RxJava types and have the asynchronous values they produce rendered to the response. For additional details, see Streaming and JSON rendering.

You can combine @ResponseBody methods with JSON serialization views. See Jackson JSON for details.

You can use the HTTP message codecs option of the WebFlux Config to configure or customize message writing.

ResponseEntity

Web MVC

ResponseEntity is like @ResponseBody but with status and headers. For example:

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 supports using a single value reactive type to produce the ResponseEntity asynchronously, and/or single and multi-value reactive types for the body. This allows a variety of async responses with ResponseEntity as follows:

  • ResponseEntity<Mono<T>> or ResponseEntity<Flux<T>> make the response status and headers known immediately while the body is provided asynchronously at a later point. Use Mono if the body consists of 0..1 values or Flux if it can produce multiple values.
  • Mono<ResponseEntity<T>> provides all three — response status, headers, and body, asynchronously at a later point. This allows the response status and headers to vary depending on the outcome of asynchronous request handling.
  • Mono<ResponseEntity<Mono<T>>> or Mono<ResponseEntity<Flux<T>>> are yet another possible, albeit less common alternative. They provide the response status and headers asynchronously first and then the response body, also asynchronously, second.

Jackson JSON

Spring offers support for the Jackson JSON library.

JSON Views

Web MVC

Spring WebFlux provides built-in support for Jackson’s Serialization Views (英語), which allows rendering only a subset of all fields in an Object. To use it with @ResponseBody or ResponseEntity controller methods, you can use Jackson’s @JsonView annotation to activate a serialization view class, as the following example shows:

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
}