1.4. Annotated Controllers
Spring WebFluxはアノテーションベースのプログラミングモデルを提供しており、@Controller
と@RestController
コンポーネントは、リクエストマッピング・リクエスト入力・例外ハンドリングなど、を表現するのにアノテーションを用います。コントローラには適当なメソッドを作りますが、ベースクラスの拡張や特定インタフェースの実装は不要です。
以下が例です。
@RestController public class HelloController { @GetMapping("/hello") public String handle() { return "Hello WebFlux"; } }
この例ではメソッドはレスポンスボディに書き込むStringを返しています。
1.4.1. @Controller
一般的な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
@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
リクエストのマッピングにはワイルドカードパターンを使います。
?
は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パスマッチングにPathPattern
とPathPatternParser
を使用します。これらはどちらもspring-web
のもので、実行時に大量のURIパスパターンマッチングを行うwebアプリケーションのHTTP URLパスに使うために作られたものです。
Spring WebFluxはサフィックスパターンマッチをサポートせず、対照的に、Spring MVCは/person
が/person.*
にマッチするなどのマッピングをします。
URLベースのコンテンツネゴシエーションがもし必要であれば、クエリパラメータを推奨します。クエリパラメータは、単純かつ明示的で、URIパスベースで脆弱性を作る危険性を減らせます。
Pattern Comparison
URLに複数のパターンがマッチする場合、最もマッチするものを決定する必要があります。
PathPattern.SPECIFICITY_COMPARATOR
すべてのパターンについて、URI変数とワイルドカードの個数に基づくスコア算出を行い、URI変数のスコアはワイルドカードより低いです。トータルスコアがより低いパターンが選ばれます。もし二つのパターンが同一スコアの場合、最も長い方が選ばれます。
**
, {*varName}
などcatch-allのパターンはスコア計算から除外されて常にソート順が末尾になります。もし二つのパターンともcatch-allの場合、最も長い方が選ばれます。
Consumable Media Types
リクエストのContent-Type
を用いてリクエストマッピングを絞り込むことが出来ます。
@PostMapping(path = "/pets", consumes = "application/json") public void addPet(@RequestBody Pet pet) { // ... }
consumes属性は否定表現もサポートしていおり、!text/plain
は"text/plain"以外のコンテンツタイプという意味になります。
クラスレベルで共通のconsumes属性を宣言できます。他のリクエストマッピングの属性とは異なり、これをクラスレベルで使う場合、メソッドレベルのconsumes属性はクラスレベルの宣言の拡張ではなくオーバライドします。
MediaType
にAPPLICATION_JSON_VALUE
, APPLICATION_JSON_UTF8_VALUE
など一般的なメディアタイプの定数があります。
Producible Media Types
Accept
リクエストヘッダーでリクエストマッピングや、コントローラーのメソッドがプロデュースするコンテンツタイプのリストを、絞り込めます。
@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8") @ResponseBody public Pet getPet(@PathVariable String petId) { // ... }
メディアタイプには文字列を指定します。否定表現、例えば!text/plain
を使用可能で、これは"text/plain"以外の意味になります。
クラスレベルで共通のproduces属性を宣言できます。他のリクエストマッピングの属性とは異なり、これをクラスレベルで使う場合、メソッドレベルのproduces属性はクラスレベルの宣言の拡張ではなくオーバライドします。
MediaType
にAPPLICATION_JSON_VALUE
, APPLICATION_JSON_UTF8_VALUE
など一般的なメディアタイプの定数があります。
Parameters and Headers
クエリパラメータの条件によりリクエストマッピングを絞り込めます。クエリパラメータが存在するかどうか("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
@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
@RequestMapping
ハンドラーメソッドのシグネチャは柔軟に決められ、コントローラメソッドの引数と戻り値にはいくつかの選択肢があります。
Method arguments
以下のテーブルはコントローラーメソッド引数で使用可能な一覧です。
Reactive型(Reactor, RxJava, その他)をブロッキングI/Oを必要とする引数、例えばリクエストボディの読み込み、で使用できます。この点はdescription列に記しています。ブロッキングを必要としない引数ではReactive型は使用できません。
JDK 1.8のjava.util.Optinal
はrequired
属性、たとえば@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型サポート対象。MultipartとMultipart 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エラー。Errors やBindingResult はvalidation対象メソッド引数の直後に宣言する必要がある。 |
SessionStatus + クラスレベル @SessionAttributes |
あるフォーム処理の完了時に、クラスレベルの@SessionAttributes で宣言したセッション属性のクリーンアップを指示する。@SessionAttributesを参照。 |
UriComponentsBuilder |
相対URLを現在のリクエストのホスト・ポート・スキーマ・コンテキストパスを考慮したものにする。サーブレットマッピングのリテラルパートはForwarded とX-Forwarded-* ヘッダーに入る。 |
@SessionAttribute |
任意のセッション属性へのアクセス用。クラスレベルの@SessionAttributes 宣言でセッションに持たせるモデル属性以外のセッション属性にもアクセスできます。 |
@RequestAttribute |
リクエスト属性へのアクセス用。@RequestAttributeを参照。 |
その他 | メソッド引数が上記いずれにもマッチしない場合、デフォルトでは、BeanUtils#isSimplePropertyに該当する単純な型の場合は@RequestParam に解決される。そうでない場合は@ModelAttribute になる。 |
Return values
以下の表はコントローラのメソッドで使用可能な戻り値です。すべての戻り値で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> など)で、ServerHttpResponse ・ ServerWebExchange 引数・@ResponseStatus のいずれかの場合、戻り値型(もしくはnull )はレスポンスを完全に処理したと見なします。コントローラーがpositive ETagかlastModifiedタイムスタンプチェックを行う場合も同様に扱われます。上記いずれにも当てはまらない場合、void はRESTコントローラーでは"no response body"を意味し、HTMLコントローラーではデフォルトのビュー名になります。 |
Flux<ServerSentEvent> ,Observable<ServerSentEvent> ,その他のReactive型 |
server-sentイベントの送出。データを書き込む必要がある場合のみ、SeverSentEvent ラッパーは省略可能です。(ただし、text/event-stream は、リクエストするか、属性のプロデュース経由でマッピングに宣言する必要があります。*4) |
それ以外の戻り値 | 上記いずれにも該当しない場合、デフォルトでは、String かvoid の場合ビュー名として扱われます(voidはデフォルトビュー名が適用)。また、BeanUtils#isSimplePropertyに該当する単純な型でない場合はモデルに追加される属性になります。それ以外はunresolvedのままです。 |
Type Conversion
コントローラメソッドの引数でSpringの入力リクエストを表現するアノテーション、例えば@RequestParam
, @RequestHeader
, @PathVariable
, @MatrixVariable
, @CookieValue
、は引数をString
以外で宣言する場合は型変換を基本的には必要とします。
このような場合は型変換は設定されているコンバーターを自動的に適用します。デフォルトでは、単純な型、int
, long
, Date
、などです。型変換はWebDataBinder
でカスタマイズするか、FormattingConversionService
でFormatters
を登録します。Spring Field Formatting参照。
Matrix variables
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
コントローラでクエリパラメータをメソッド引数にバインドするには@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
のメソッド引数のデフォルトは必須ですが、@RequestParam
のrequired
をfalse
にするかjava.util.Optional
でラップできます。
ターゲットメソッドの引数型がString
では無い場合、型変換を自動的に適用します。mvc-ann-typeconversion。
@RequestParam
をMap<String, String>
やMultiValueMap<String, String>
で宣言する場合、そのマップにはすべてのクエリパラメータが入ります。
属性を設定するのに@RequestParam
は必ずしも必須ではない点に注意してください。デフォルトでは、引数は、BeanUtils#isSimplePropertyが単純な値型と判定し、かつ、その他の引数リゾルバで解決しない場合、@RequestParam
を付与したと見なします。
@RequestHeader
コントローラーでリクエストヘッダをメソッド引数にバインドするには@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-Encoding
とKeep-Alive
の値を取得します。
@GetMapping("/demo") public void handle( @RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { //... }
ターゲットメソッドの引数型がString
では無い場合、型変換を自動的に適用します。mvc-ann-typeconversion。
@RequestHeader
をMap<String, String>
やMultiValueMap<String, String>
やHttpHeaders
で宣言する場合、そのマップにはすべてのヘッダー値が入ります。
カンマ区切り文字列をStringもしくは型変換システムが利用可能な型のarray/collectionへの変換をビルトインで利用可能です。例えば、@RequestHeader("Accept")
のメソッド引数はString
以外にString[]
やList<String>
も可能です。
@CookieValue
コントローラーでHTTPクッキーの値をメソッド引数にバインドするには@CookieValue
を使います。
以下のクッキーがあるとします。
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
以下はクッキーの値を取得するコード例です。
@GetMapping("/demo") public void handle(@CookieValue("JSESSIONID") String cookie) { //... }
ターゲットメソッドの引数型がString
では無い場合、型変換を自動的に適用します。mvc-ann-typeconversion。
@ModelAttribute
メソッド引数でモデルの属性にアクセスには@ModelAttribute
を使います。存在しない場合はインスタンス化します。モデル属性はフォームフィールドの名前と一致するクエリパラメータの値を同じに出来ます。これはデータバインディングと呼ばれ、クエリパラメータとフォームフィールド間のパースと変換の手間を省くものです。
@PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@ModelAttribute 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 validationとSpring 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
@SessionAttributes
はWebSession
にモデル属性を格納するのに使用します。ある特定のコントローラーで使うセッション属性を宣言する型レベルアノテーションです。基本的には、モデル属性の名前かモデル属性の型を列挙し、後続のリクエストでアクセスするためにセッションへ透過的な保存が行われます。
@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
グローバル管理、つまり対象コントローラの外(フィルタなど)、で既に存在するセッション属性にアクセスする場合、存在してもしていなくても、メソッド引数で@SessionAttribute
を使います。
@GetMapping("/") public String handle(@SessionAttribute User user) { // ... }
セッション属性の追加や削除を行う場合はコントローラメソッドにWebSession
をインジェクションします。
コントローラーの処理フローの一部をセッションのモデル属性に一時保存する場合、@SessionAttributesで説明するSessionAttributes
を使用してください。
@RequestAttribute
@SessionAttribute
同様に、@RequestAttribute
で既に存在する、WebFilter
などが作成する、リクエスト属性にアクセスします。
@GetMapping("/") public String handle(@RequestAttribute Client client) { // ... }
Multipart
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) { // ... }
ストリーミングでマルチパートをシーケンシャルにアクセスするには、@RequestBody
とFlux<Part>
を使います。
@PostMapping("/") public String handle(@RequestBody Flux<Part> parts) { // ... }
@RequestPart
は、Standard Bean Validationを適用するための、javax.validation.Valid
かSpringの@Validated
を一緒に使えます。デフォルトではvalidationエラーはWebExchangeBindException
となり400 (BAD_REQUEST)レスポンスになります。もしくは、Errors
かBindingResult
引数によりコントローラ内でvalidationエラーを処理できます。
@PostMapping("/") public String handle(@Valid @RequestPart("meta-data") MetaData metadata, BindingResult result) { // ... }
@RequestBody
リクエストボディを読み込み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 ConfigのHTTP message codecsを使います。
@RequestBody
は、Standard Bean Validationを適用するための、javax.validation.Valid
かSpringの@Validated
を一緒に使えます。デフォルトではvalidationエラーはWebExchangeBindException
となり400 (BAD_REQUEST)レスポンスになります。もしくは、Errors
かBindingResult
引数によりコントローラ内でvalidationエラーを処理できます。
@PostMapping("/accounts") public void handle(@Valid @RequestBody Account account, BindingResult result) { // ... }
HttpEntity
HttpEntity
は基本的には@RequestBodyの使い方と同じですが、こちらはリクエストヘッダ―とボディを公開するコンテナオブジェクトがベースになっています。
@PostMapping("/accounts") public void handle(HttpEntity<Account> entity) { // ... }
@ResponseBody
@ResponseBody
はHttpMessageWriterでシリアライズしたレスポンスボディを返すメソッドで使います。
@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 ConfigのHTTP message codecsを使います。
ResponseEntity
ResponseEntity
は基本的には@ResponseBodyの使い方と同じですが、こちらはリクエストヘッダ―とボディを公開するコンテナオブジェクトがベースになっています。
@PostMapping("/something") public ResponseEntity<String> handle() { // ... URI location = ... return new ResponseEntity.created(location).build(); }
Jackson JSON
Jackson serialization views
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
@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); }
名前を明示的に指定しない場合、デフォルト名はConventionsのJavadocの説明にあるとおり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
文字列ベースのリクエスト(例:リクエストパラメータ、パス変数、ヘッダー、クッキーなど)を表すメソッド引数の型変換のカスタマイズをするには@Controller
もしくは@ControllerAdvice
クラスで@InitBinder
のメソッドを作ります。型変換は@ModelAttribute
(つまりcommand objects)のリクエストパラメータのデータバインディングにも適用されます。
@InitBinder
のメソッドは、コントローラ固有のjava.bean.PropertyEditor
かSpringのConverter
とFormatter
、を登録できます。また、WebFlux Java configを使用してグローバルなFormattingConversionService
にConverter
とFormatter
を登録できます。
@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
通常、@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 {}
なお、上記のセレクタは実行時に評価されるので、過度の使用はパフォーマンスに影響を及ぼす可能性があります。詳細は@ControllerAdviceのJavadocを参照。
*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.が原文。訳に自信がない