kagamihogeの日記

kagamihogeの日記です。

Web on Reactive Stack(version 5.0.3.RELEASE) 1. Spring WebFluxをテキトーに訳した(1.4)

1.4. Annotated Controllers

Spring MVCと同等

Spring WebFluxはアノテーションベースのプログラミングモデルを提供しており、@Controller@RestControllerコンポーネントは、リクエスマッピング・リクエスト入力・例外ハンドリングなど、を表現するのにアノテーションを用います。コントローラには適当なメソッドを作りますが、ベースクラスの拡張や特定インタフェースの実装は不要です。

以下が例です。

@RestController
public class HelloController {

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

この例ではメソッドはレスポンスボディに書き込むStringを返しています。

1.4.1. @Controller

Spring MVCと同等

一般的なSpringのbeanでコントローラーを定義します。ステレオタイプ@Controllerは自動検出を行うものです。これはクラスパスの@Componentクラスを検出するSpringの汎用サポート機能で、該当のbean定義を自動登録します。また、コントローラのステレオタイプとしても振る舞うので、これはそのクラスがwebコンポーネントの役割を担うことを指します。

@Controllerの自動検出を有効化するには、Java configurationにコンポーネントスキャンを追加します。

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

    // ...
}

@RestController@Controller@ResponseBodyから成る複合アノテーションです。これはコントローラのすべてのメソッドは型レベルの@ResponseBodyアノテーションを引き継ぎ、それらのメソッドはレスポンスボディに書き込む、という意味になります(対してmodel-and-viewレンダリングもある)。

1.4.2. Request Mapping

Spring MVCと同等

@RequestMappingはリクエストをコントローラのメソッドにマッピングするのに使います。URL・HTTPのメソッド・リクエストパラメータ・ヘッダ・メディアタイプなどでマッチする様々な属性があります。共通のマッピングを表現するにはクラスレベルで使用し、特定のエンドポイントマッピングに落とし込むにはメソッドレベルで使用します。

また、@RequestMappingには特定のHTTPメソッド用のショートカットがあります。

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

これらは複合アノテーションで、アノテーション自身に@RequestMappingが付けられています。これらは通常メソッドレベルで使います。クラスレベルには@RequestMappingが共通のマッピングを表すのに便利です。

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

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

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}

URI Patterns

Spring MVCと同等

リクエストのマッピングにはワイルドカードパターンを使います。

  • ?は1文字とマッチ
  • *はパスセグメント内で0文字以上とマッチ
  • **は0個以上のパスセグメントにマッチ

またURL変数とその値にアクセスする@PathVariableを宣言できます。

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

URL変数はクラスとメソッドレベルで使えます。

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

    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}

URI変数は自動的に適当な型に変換され、出来ない場合はTypeMismatchExceptionをスローします。単純な型のint, long, Dateはデフォルトでサポートしており、その他のデータ型は自前で登録します。型変換Binder Methodsを参照してください。

URI変数には明示的な名前、例えば@PathVariable("customId")、を付けられますが、名前と同じ場合には省略可能です。省略するには、デバッグ情報を付けるか、Java 8では-parametersフラグを付けてコンパイルします。

URI変数宣言{*varName}は、ゼロ個以上のパスセグメントにマッチします。例えば、/resources/{*path}/resources/のすべてのファイルにマッチし、"path"変数は完全な相対パスになります*1

URI変数宣言{varName:regex}正規表現付きのもので、例えばURL /spring-web-3.0.5 .jar"の場合、以下のメソッドはname, version, extentionを抽出します。

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

URIパスパターンにはプレースホルダ${...}を入れることも可能で、これは開始時にPropertyPlaceHolderConfigurerを介して、ローカル・システム・環境などのプロパティソースで解決されます。使用例としては、何らかの外部設定に基づくベースURLをパラメータ化するなどです。

Spring WebFluxはURIパスマッチングにPathPatternPathPatternParserを使用します。これらはどちらもspring-webのもので、実行時に大量のURIパスパターンマッチングを行うwebアプリケーションのHTTP URLパスに使うために作られたものです。

Spring WebFluxはサフィックスパターンマッチをサポートせず、対照的に、Spring MVC/person/person.*にマッチするなどのマッピングをします。

URLベースのコンテンツネゴシエーションがもし必要であれば、クエリパラメータを推奨します。クエリパラメータは、単純かつ明示的で、URIパスベースで脆弱性を作る危険性を減らせます。

Pattern Comparison

Spring MVCと同等

URLに複数のパターンがマッチする場合、最もマッチするものを決定する必要があります。

PathPattern.SPECIFICITY_COMPARATOR

すべてのパターンについて、URI変数とワイルドカードの個数に基づくスコア算出を行い、URI変数のスコアはワイルドカードより低いです。トータルスコアがより低いパターンが選ばれます。もし二つのパターンが同一スコアの場合、最も長い方が選ばれます。

**, {*varName}などcatch-allのパターンはスコア計算から除外されて常にソート順が末尾になります。もし二つのパターンともcatch-allの場合、最も長い方が選ばれます。

Consumable Media Types

Spring MVCと同等

リクエストのContent-Typeを用いてリクエスマッピングを絞り込むことが出来ます。

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
    // ...
}

consumes属性は否定表現もサポートしていおり、!text/plainは"text/plain"以外のコンテンツタイプという意味になります。

クラスレベルで共通のconsumes属性を宣言できます。他のリクエスマッピングの属性とは異なり、これをクラスレベルで使う場合、メソッドレベルのconsumes属性はクラスレベルの宣言の拡張ではなくオーバライドします。

MediaTypeAPPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUEなど一般的なメディアタイプの定数があります。

Producible Media Types

Spring MVCと同等

Acceptリクエストヘッダーでリクエスマッピングや、コントローラーのメソッドがプロデュースするコンテンツタイプのリストを、絞り込めます。

@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}

メディアタイプには文字列を指定します。否定表現、例えば!text/plainを使用可能で、これは"text/plain"以外の意味になります。

クラスレベルで共通のproduces属性を宣言できます。他のリクエスマッピングの属性とは異なり、これをクラスレベルで使う場合、メソッドレベルのproduces属性はクラスレベルの宣言の拡張ではなくオーバライドします。

MediaTypeAPPLICATION_JSON_VALUE, APPLICATION_JSON_UTF8_VALUEなど一般的なメディアタイプの定数があります。

Parameters and Headers

Spring MVCと同等

クエリパラメータの条件によりリクエスマッピングを絞り込めます。クエリパラメータが存在するかどうか("myParam")、存在しないかどうか("!myParam")、特定の値かどうか("myParam=myValue")、が使えます。

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
    // ...
}

リクエストヘッダ―でも同じような条件が使えます。

@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
    // ...
}

HTTP HEAD, OPTIONS

Spring MVCと同等

@GetMapping - と@RequestMapping(method=HttpMethod.GET)はリクエスマッピングでは透過的にHTTP HEADをサポートします*2。コントローラーのメソッドには特に手を入れる必要はありません。HttpHandlerのサーバアダプタが適用されるレスポンスラッパは、"Content-Length"ヘッダーに書き込みバイト数をセットし、レスポンスには何も書き込みません。

デフォルトではHTTP OPTIONSは、マッチするURLパターンのすべての@RequestMapping methodsに"Allow"レスポンスヘッダを指定することで処理されるようになります。

HTTPメソッド宣言の無い@RequestMappingでは、"Allow"ヘッダーは"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"がセットされます。コントローラのメソッドは、特定のHTTPメソッド用のアノテーション@GetMapping, @PostMappingなど、使用するHTTPメソッドを常に宣言するようにしてください。

@RequestMappingのメソッドは明示的にHTTP HEADとHTTP OPTIONSにマッピングされますが、一般的な使用法ではありません。

1.4.3. Handler methods

Spring MVCと同等

@RequestMappingハンドラーメソッドのシグネチャは柔軟に決められ、コントローラメソッドの引数と戻り値にはいくつかの選択肢があります。

Method arguments

Spring MVCと同等

以下のテーブルはコントローラーメソッド引数で使用可能な一覧です。

Reactive型(Reactor, RxJava, その他)をブロッキングI/Oを必要とする引数、例えばリクエストボディの読み込み、で使用できます。この点はdescription列に記しています。ブロッキングを必要としない引数ではReactive型は使用できません。

JDK 1.8のjava.util.Optinalrequired属性、たとえば@RequestParam, @RequestHeader、を持つアノテーションのメソッド引数と組み合わせて使用できます。また、required=falseと同等になります。

Controller method argument Description
ServerWebExchange ServerWebExchangeのフルアクセス。このクラスは、HTTPリクエスト・レスポンス・リクエストとセッション属性・checkNotModifiedメソッド、などのコンテナ
ServerHttpRequest, ServerHttpResponse HTTPリクエストやレスポンスへのアクセス用
WebSession セッションへのアクセス用。属性が追加されない限り、新規セッションの開始を強制しない。reactive型サポート対象。
java.security.Principal 現在の認証済みユーザ。基本的には既知のPrincipal実装になる。reactive型サポート対象。
org.springframework.http.HttpMethod リクエストのHTTPメソッド
java.util.Locale 使用可能な中で最も特定的なLocaleResolverで決定される現在のリクエストのロケール
Java 6+: java.util.TimeZone
Java 8+: java.time.ZoneId
LocaleContextResolverが決定する現在のリクエストに関連付けられたタイムゾーン
@PathVariable URIテンプレート変数へのアクセス用。URI Patterns参照。
@MatrixVariable URIパスセグメント内のname-valueペアへのアクセス用。Matrix variables
@RequestParam Servletリクエストパラメータへのアクセス用。パラメータ値はメソッド引数の型に変換される。@RequestParamを参照。
属性の設定には@RequestParam以外の方法もあります。この表の"Any other argument"も参照。
@RequestHeader リクエストヘッダ―へのアクセス用。ヘッダー値はメソッド引数の型に変換される。@RequestHeader
@CookieValue クッキーへのアクセス用。クッキーの値はメソッド引数の型に変換される。@CookieValue
@RequestBody HTTPリクエストボディへのアクセス用。ボディコンテンツはHttpMessageReaderを使用するメソッド引数の型に変換される。reactive型サポート対象。
HttpEntity<B> リクエストヘッダ―とボディへのアクセス用。ボディはHttpMessageReaderに変換される。reactive型サポート対象。
@RequestPart "multipart/form-data"リクエストのpart部分へのアクセス用。reactive型サポート対象。MultipartMultipart Readerを参照。
java.util.Map,
org.springframework.ui.Model,
org.springframework.ui.ModelMap
HTMLコントローラーおよびビューのテンプレートで使うモデルへのアクセス用。
@ModelAttribute モデル(無ければインスタンス化される)の属性へのアクセス用。データバインディングとvalidationが適用される。@ModelAttribute, Model Methods, Binder Methodsを参照。
属性の設定には@ModelAttribute以外の方法もあります。この表の"Any other argument"も参照。
Errors, BindingResult command object(@ModelAttribute引数のこと)のvalidationとデータバインディングのエラー、もしくは、@RequestBodyもしくは@RequestPartのvalidationエラー。ErrorsBindingResultはvalidation対象メソッド引数の直後に宣言する必要がある。
SessionStatus +
クラスレベル@SessionAttributes
あるフォーム処理の完了時に、クラスレベルの@SessionAttributesで宣言したセッション属性のクリーンアップを指示する。@SessionAttributesを参照。
UriComponentsBuilder 相対URLを現在のリクエストのホスト・ポート・スキーマ・コンテキストパスを考慮したものにする。サーブレットマッピングリテラルパートはForwardedX-Forwarded-*ヘッダーに入る。
@SessionAttribute 任意のセッション属性へのアクセス用。クラスレベルの@SessionAttributes宣言でセッションに持たせるモデル属性以外のセッション属性にもアクセスできます。
@RequestAttribute リクエスト属性へのアクセス用。@RequestAttributeを参照。
その他 メソッド引数が上記いずれにもマッチしない場合、デフォルトでは、BeanUtils#isSimplePropertyに該当する単純な型の場合は@RequestParamに解決される。そうでない場合は@ModelAttributeになる。
Return values

Spring MVCと同等

以下の表はコントローラのメソッドで使用可能な戻り値です。すべての戻り値でReactive型、Reactor, RxJava, その他、が使用可能です。

Controller method return value Description
@ResponseBody HttpMessageWriterエンコードしてレスポンスに書き込まれる。@ResponseBody参照。
HttpEntity<B>, ResponseEntity<B> HttpMessageWriterエンコードするボディとHTTPヘッダーを含む完全なレスポンス。ResponseEntity参照。
HttpHeaders ボディ無しでヘッダーを持つレスポンスを返す。
String ViewResolverで解決するビュー名で、command objectsと@ModelAttributeのメソッドが決定する暗黙モデルで使用される*3。ハンドラメソッドではModel引数を宣言してモデルを使用します(上記参照)。
View 暗黙モデルと一緒にレンダリングに使うViewインスタンス。ハンドラメソッドではModel引数を宣言してモデルを使用します(上記参照)。
java.util.Map, org.springframework.ui.Model 暗黙モデルに追加される属性。ビュー名はリクエストパスに基づいて暗黙的に決定される。
@ModelAttribute モデルに追加される属性。ビュー名はリクエストパスに基づいて暗黙的に決定される。
@ModelAttributeの使用は任意です。この表の「それ以外の戻り値」を参照。
Rendering モデルとビューをレンダリングするシナリオ用のAPI
void voidのメソッドは、おそらく非同期(Mono<Void>など)で、ServerHttpResponseServerWebExchange引数・@ResponseStatusのいずれかの場合、戻り値型(もしくはnull)はレスポンスを完全に処理したと見なします。コントローラーがpositive ETagかlastModifiedタイムスタンプチェックを行う場合も同様に扱われます。上記いずれにも当てはまらない場合、voidはRESTコントローラーでは"no response body"を意味し、HTMLコントローラーではデフォルトのビュー名になります。
Flux<ServerSentEvent>,
Observable<ServerSentEvent>,
その他のReactive型
server-sentイベントの送出。データを書き込む必要がある場合のみ、SeverSentEventラッパーは省略可能です。(ただし、text/event-streamは、リクエストするか、属性のプロデュース経由でマッピングに宣言する必要があります。*4
それ以外の戻り値 上記いずれにも該当しない場合、デフォルトでは、Stringvoidの場合ビュー名として扱われます(voidはデフォルトビュー名が適用)。また、BeanUtils#isSimplePropertyに該当する単純な型でない場合はモデルに追加される属性になります。それ以外はunresolvedのままです。

Type Conversion

Spring MVCと同等

コントローラメソッドの引数でSpringの入力リクエストを表現するアノテーション、例えば@RequestParam, @RequestHeader, @PathVariable, @MatrixVariable, @CookieValue、は引数をString以外で宣言する場合は型変換を基本的には必要とします。

このような場合は型変換は設定されているコンバーターを自動的に適用します。デフォルトでは、単純な型、int, long, Date、などです。型変換はWebDataBinderでカスタマイズするか、FormattingConversionServiceFormattersを登録します。Spring Field Formatting参照。

Matrix variables

Spring MVCと同等

RFC 3986ではパスセグメント内のname-valueペアについての提案です。Spring WebFluxではTim Berners-Leeの過去ポストに基づきこれを"matrix variables"と呼称していますが、URIパスパラメータ(URI path parameters)とも呼称します。

Matrix variablesはパスセグメント内に配置可能で、各変数はセミコロンで区切り、複数の値はカンマで区切ります。例:"/cars;color=red,green;year=2012"。複数の値は変数名を繰り返すことでも指定可能です。例:"color=red;color=green;color=blue"

Spring MVCとは異なり、WebFluxではURLのmatrix variablesの有無はリクエスマッピングに影響を与えません。言い換えると、可変要素をマスクするためのURI変数を用意する必要はありません。コントローラーメソッドでmatrix variablesにアクセスする場合、matrix variablesを含むパスセグメントにURI変数を追加する必要があります。以下がその例です。

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

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

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

すべてのパスセグメントがmatrix variablesを含む場合、matrix variableを含みうるパス変数のあいまいさを無くす必要があります。

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

matrix variableにはデフォルト値を指定可能です。

// GET /pets/42

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

    // q == 1
}

すべてのmatrix variablesを取得するにはMultiValueMapを使います。


// 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]
}

@RequestParam

Spring MVCと同等

コントローラでクエリパラメータをメソッド引数にバインドするには@RequestParamを使います。以下が使用例です。

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

    // ...

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

    // ...

}

Servlet APIの"request paramater"の概念は、クエリパラメータ・フォームデータ・マルチパートをを一つにまとめていますが、これと異なりWebFluxでは、ServerWebExchangeを介してそれぞれにアクセスします。@RequestParamはクエリパラメータにだけバインドしますが、command objectにクエリパラメータ・フォームデータ・マルチパートをデータバインディングできます。

@RequestParaのメソッド引数のデフォルトは必須ですが、@RequestParamrequiredfalseにするかjava.util.Optionalでラップできます。

ターゲットメソッドの引数型がStringでは無い場合、型変換を自動的に適用します。mvc-ann-typeconversion

@RequestParamMap<String, String>MultiValueMap<String, String>で宣言する場合、そのマップにはすべてのクエリパラメータが入ります。

属性を設定するのに@RequestParamは必ずしも必須ではない点に注意してください。デフォルトでは、引数は、BeanUtils#isSimplePropertyが単純な値型と判定し、かつ、その他の引数リゾルバで解決しない場合、@RequestParamを付与したと見なします。

@RequestHeader

Spring MVCと同等

コントローラーでリクエストヘッダをメソッド引数にバインドするには@RequestHeaderを使います。

以下のヘッダーがあると仮定します。

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-EncodingKeep-Aliveの値を取得します。

@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

ターゲットメソッドの引数型がStringでは無い場合、型変換を自動的に適用します。mvc-ann-typeconversion

@RequestHeaderMap<String, String>MultiValueMap<String, String>HttpHeadersで宣言する場合、そのマップにはすべてのヘッダー値が入ります。

カンマ区切り文字列をStringもしくは型変換システムが利用可能な型のarray/collectionへの変換をビルトインで利用可能です。例えば、@RequestHeader("Accept")のメソッド引数はString以外にString[]List<String>も可能です。

@CookieValue

Spring MVCと同等

コントローラーでHTTPクッキーの値をメソッド引数にバインドするには@CookieValueを使います。

以下のクッキーがあるとします。

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

以下はクッキーの値を取得するコード例です。

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
    //...
}

ターゲットメソッドの引数型がStringでは無い場合、型変換を自動的に適用します。mvc-ann-typeconversion

@ModelAttribute

Spring MVCと同等

メソッド引数でモデルの属性にアクセスには@ModelAttributeを使います。存在しない場合はインスタンス化します。モデル属性はフォームフィールドの名前と一致するクエリパラメータの値を同じに出来ます。これはデータバインディングと呼ばれ、クエリパラメータとフォームフィールド間のパースと変換の手間を省くものです。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

上記Petインスタンスは以下のように解決が行われます。

  • Model Methodsで追加済みの場合、そのモデルから取得。
  • @SessionAttributesのHTTPセッションから取得。
  • デフォルトコンストラクタの呼び出しから取得。
  • クエリパラメータかフォームフィールドにマッチする引数で"primary constructor"を呼び出して取得。引数名はJavaBeansの@ConstructorPropertiesバイトコード内にある実行時保持型のパラメータ名(runtime-retained parameter names )で決定される。

モデル属性のインスタンスを取得後に、データバインディングを適用します。WebExchangeDataBinderクラスは、クエリパラメータ名とフォームフィールドを、ターゲットオブジェクトのフィールド名とマッチングします。マッチするフィールドは適宜型変換後に値が入ります。データバインディング(とvalidation)の詳細はValidationを参照。データバインディングのカスタマイズの詳細はBinder Methodsを参照。

データバインディングはエラーになる可能性があります。デフォルトではWebExchangeBindExceptionになりますが、コントローラーメソッドでそうしたエラーを処理するには@ModelAttributeのすぐ横にBindingResult引数を追加します。

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

Validationを行うにはデータバインディング後にjavax.validation.ValidかSpringの@Validatedを付与します(詳細はBean validationSpring validationを参照)。

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

Spring WebFluxは、Spring MVCと異なり、モデルにreactive型(Mono<Account>io.reactivex.Single<Account>など)を使えます。@ModelAttributeにはreactive型ラッパーを使用しても良いし、しなくても良いです。必要に応じて実際の値に解決されます*5。なお、ここでBindingResultを使うには、上で見たように、reactive型ラッパを使わず、BindingResultの前に@ModelAttributeを宣言する必要があります。もしくは、reactive型を通してエラーを処理します。

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

属性を設定するのに@ModelAttributeは必ずしも必須ではない点に注意してください。

デフォルトでは、引数は、BeanUtils#isSimplePropertyが単純な値型では無いと判定し、かつ、その他の引数リゾルバで解決しない場合、@ModelAttributeを付与したと見なします。

@SessionAttributes

Spring MVCと同等

@SessionAttributesWebSessionにモデル属性を格納するのに使用します。ある特定のコントローラーで使うセッション属性を宣言する型レベルアノテーションです。基本的には、モデル属性の名前かモデル属性の型を列挙し、後続のリクエストでアクセスするためにセッションへ透過的な保存が行われます。

@Controller
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

初回リクエストで"pet"という名前のモデル属性をモデルに追加し、自動的に昇格してWebSessionに保存が行われます。これを削除するには別のコントローラメソッドの引数でSessionStatusを使用します。

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

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
            status.setComplete();
            // ...
        }
    }
}

@SessionAttribute

Spring MVCと同等

グローバル管理、つまり対象コントローラの外(フィルタなど)、で既に存在するセッション属性にアクセスする場合、存在してもしていなくても、メソッド引数で@SessionAttributeを使います。

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

セッション属性の追加や削除を行う場合はコントローラメソッドにWebSessionをインジェクションします。

コントローラーの処理フローの一部をセッションのモデル属性に一時保存する場合、@SessionAttributesで説明するSessionAttributesを使用してください。

@RequestAttribute

Spring MVCと同等

@SessionAttribute同様に、@RequestAttributeで既に存在する、WebFilterなどが作成する、リクエスト属性にアクセスします。

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

Multipart

Spring MVCと同等

Multipart Readerの解説の通り、ServerWebExchangeでマルチパートにアクセスします。コントローラーでファイルアップロードのフォームを処理するベストな方法はcommand objectのデータバインディングを使います。

class MyForm {

    private String name;

    private MultipartFile file;

    // ...

}

@Controller
public class FileUploadController {

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

}

RESTfulサービスの非ブラウザクライアントからもマルチパートリクエストをサブミットできます。

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

JSONをデシリアライズする@RequestPartで"meta-data"にアクセスできます。この処理は設定したHTTP Message Codecsを使用します。

@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") FilePart file) {
    // ...
}

ストリーミングでマルチパートをシーケンシャルにアクセスするには、@RequestBodyFlux<Part>を使います。

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

@RequestPartは、Standard Bean Validationを適用するための、javax.validation.ValidかSpringの@Validatedを一緒に使えます。デフォルトではvalidationエラーはWebExchangeBindExceptionとなり400 (BAD_REQUEST)レスポンスになります。もしくは、ErrorsBindingResult引数によりコントローラ内でvalidationエラーを処理できます。

@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
        BindingResult result) {
    // ...
}

@RequestBody

Spring MVCと同等

リクエストボディを読み込みHttpMessageReader経由でObjectにデシリアライズするには@RequestBodyを使います。以下は@RequestBodyの例です。

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

Spring MVCと異なり、WebFluxでは、@RequestBodyメソッド引数はreactive型と完全なノンブロッキング読込およびストリーミングを行えます。

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

メッセージリーダのカスタマイズや設定変更はWebFlux ConfigHTTP message codecsを使います。

@RequestBodyは、Standard Bean Validationを適用するための、javax.validation.ValidかSpringの@Validatedを一緒に使えます。デフォルトではvalidationエラーはWebExchangeBindExceptionとなり400 (BAD_REQUEST)レスポンスになります。もしくは、ErrorsBindingResult引数によりコントローラ内でvalidationエラーを処理できます。

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

HttpEntity

Spring MVCと同等

HttpEntityは基本的には@RequestBodyの使い方と同じですが、こちらはリクエストヘッダ―とボディを公開するコンテナオブジェクトがベースになっています。

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

@ResponseBody

Spring MVCと同等

@ResponseBodyHttpMessageWriterシリアライズしたレスポンスボディを返すメソッドで使います。

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

@ResponseBodyはすべてのコントローラーメソッドに引き継ぐためにクラスレベルでも使えます。@RestControllerでは@Controllerおよび@ResponseBodyのメタアノテーションを意味します。

@ResponseBodyはreactive型をサポートし、ReactorないしRxJavaの型を返してこれのクラスがプロデュースする非同期な値をレスポンスにレンダリングできます。JSONレンダリングの詳細はJackson JSONを参照。

@ResponseBodyメソッドはJSON serialization viewsと組み合わせることが出来ます。mvc-ann-jacksonを参照。

メッセージ書き込みもしくは設定変更にはWebFlux ConfigHTTP message codecsを使います。

ResponseEntity

Spring MVCと同等

ResponseEntityは基本的には@ResponseBodyの使い方と同じですが、こちらはリクエストヘッダ―とボディを公開するコンテナオブジェクトがベースになっています。

@PostMapping("/something")
public ResponseEntity<String> handle() {
    // ...
    URI location = ...
    return new ResponseEntity.created(location).build();
}

Jackson JSON

Jackson serialization views

Spring MVCと同等

Spring WebFluxにはJackson's Serialization Views*6用の機能が組み込まれています。これはObjectのフィールドのサブセットのみレンダリングします。これを@ResponseBodyもしくはResponseEntityのコントローラーメソッドで使うには、Jacksonの@JsonViewでserialization view classを有効化します。

@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;
    }
}

@JsonViewにはview classの配列を指定できますが、一つのコントローラーメソッドには一つのJsonViewのみ指定可能です。複数のviewを有効化する場合はcomposite interfaceを使用してください。

1.4.4. Model Methods

Spring MVCと同等

@ModelAttribute@RequestMappingメソッド引数で、モデルのObjectの生成とアクセスおよびリクエストへのバインディング、を行うために使います。

@ModelAttributeはコントローラーのメソッドレベルでも使えます。

これはリクエストの処理ではなく、リクエストの処理前

コントローラには複数の@ModelAttributeメソッドを作れます。同一コントローラ内の@RequestMappingメソッドより前に、そうしたメソッドをすべて呼び出します。@ModelAttributeメソッドは@ControllerAdviceを介して複数コントローラー間で共有できます。詳細はController Advice参照。

@ModelAttributeメソッドのシグネチャは柔軟に決められます。このメソッドで使用できる引数は@RequestMappingメソッドとほぼ同じですが、@ModelAttribute自身とリクエストボディに関するものは除きます。

以下は@ModelAttributeの例です。

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

属性を一つ追加するだけの場合は、

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}

名前を明示的に指定しない場合、デフォルト名はConventionsJavadocの説明にあるとおりObject型に基づいて決定されます。名前指定可能な方のaddAttributeメソッドを使うか、@ModelAttributeのname属性で明示的な名前を割り当てられます。

Spring WebFluxは、Spring MVCと異なり、Mono<Account>io.reactivex.Single<Account>などのreactive型をモデルで使用できます。@RequestMappingの実行時、@ModelAttributeの引数をラッパー無しで宣言すると、こうした非同期モデル属性は実際の値に透過的な解決(とモデル更新)が行われます。

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

ビューレンダリングの前に、reactive型ラッパーのモデル属性はその実際の値に解決(とモデル更新)が行われます。

@ModelAttribute@RequestMappingのメソッドレベルでも使えます。この場合は@RequestMappingメソッドの戻り値はモデル属性と解釈されます。これはHTMLコントローラのデフォルトの振る舞いであって、基本的には使わないもので、戻り値がStringでなければビュー名とは解釈されません。@ModelAttributeはモデル属性の名前をカスタマイズ可能です。

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

1.4.5. Binder Methods

Spring MVCと同等

文字列ベースのリクエスト(例:リクエストパラメータ、パス変数、ヘッダー、クッキーなど)を表すメソッド引数の型変換のカスタマイズをするには@Controllerもしくは@ControllerAdviceクラスで@InitBinderのメソッドを作ります。型変換は@ModelAttribute(つまりcommand objects)のリクエストパラメータのデータバインディングにも適用されます。

@InitBinderのメソッドは、コントローラ固有のjava.bean.PropertyEditorかSpringのConverterFormatter、を登録できます。また、WebFlux Java configを使用してグローバルなFormattingConversionServiceConverterFormatterを登録できます。

@InitBinderメソッドは@RequestMappingのメソッドと同じような引数を使用できますが、@ModelAttribute(command object)は使えません。通常、登録を行うためのWebDataBinder引数と一緒に宣言し、voidを返します。

@Controller
public class FormController {

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

    // ...
}

また、FormattingConversionServiceを介してFormatterベースのクラスを登録する場合、コントローラー固有のFormatterを登録できます。

@Controller
public class FormController {

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

    // ...
}

1.4.6. Controller Advice

Spring MVCと同等

通常、@ExceptionHandler, @InitBinder, @ModelAttributeのメソッドは、これらを宣言する@Controller(かそのクラス階層)内に適用するものです。これらのメソッドをグローバルに、複数のコントローラに、適用したい場合、@ControllerAdviceもしくは@RestControllerAdviceのクラスで宣言します。

@ControllerAdviceには@Componentがついているので、コンポーネントスキャンによってSpringのbeansとして登録されます。また、@RestControllerAdvice@ControllerAdvice@ResponseBodyのメタアノテーションで、@ExceptionHandlerのメソッドはメッセージ変換を介してレスポンスボディにレンダリングされます。

@RequestMapping@ExceptionHandlerメソッド用のinfrastructure classesが@ControllerAdviceのSpring beanを検出すると、実行時にそれらのメソッドを適用します。グローバルの(@ControllerAdviceの)@ExceptionHandlerメソッドはローカルの(@Controllerあとに適用されます。これと対照的に、グローバルの@ModelAttribute@InitBinderメソッドはローカルのそれのまえに適用されます。

デフォルトでは@ControllerAdviceメソッドはすべてのリクエスト、つまりすべてのコントローラー、に適用されますが、アノテーションの属性を通じてコントローラーのサブセットに絞り込みをかけられます。

// @RestControllerを付与するすべてのコントローラー
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// 特定パッケージ内のすべてのコントローラー
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// 特定のクラスとassignableなすべてのコントローラ
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

なお、上記セレクタは実行時に評価されるので、過度の使用はパフォーマンスに影響を及ぼす可能性があります。詳細は@ControllerAdviceJavadocを参照。

*1:variable captures the complete relative pathが原文

*2:http://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-method-head-options-support/ 実際にコード書いて試してないんで間違ってるかもしれないが、springのGETは暗黙的にHEADをサポートする、とかなんとか

*3:used together with the implicit model - determined through command objects and @ModelAttribute methods.が原文。よくわからん

*4:however text/event-stream must be requested or declared in the mapping through the produces attributeが原文。わからん…

*5:and it will be resolved accordingly, to the actual value if necessary.が原文。訳に自信がない

*6:リンク切れしてるんで適宜ぐぐる