Spring MVCのドキュメント「コントローラを実装する」を読む(2)
前回に引き続きSpring Web MVCフレームワークに関するドキュメントの訳出です。
原典は、Springフレームワーク(本体)のリファレンス・マニュアルである"Spring Framework Reference Documentation"の第5部"The Web"の第17章"Web MVC framework"の第3節(バージョン4.1.1.RELEASE現在)です。
* * *
17.3 コントローラを実装する(2)
17.3.3 @RequestMapping ハンドラ・メソッドを定義する
@RequestMapping
ハンドラ・メソッドは非常に柔軟なシグネチャを持つことができます。メソッドの引数と戻り値に使用することが可能な値はこのあとで説明します。 BindingResult
引数以外のすべての引数は任意の順序で使用できます。これについては次のセクションで説明します。
[NOTE] Spring 3.1では@RequestMapping
メソッドのために、 RequestMappingHandlerMapping
と RequestMappingHandlerAdapter
という2つのサポート・クラスが導入されました。これらを使用することは推奨〔訳注:つまり必須ではない〕ですが、Spring MVC 3.1とそれ以降で導入された新機能を利用する場合は必須となります。新しいサポート・クラスの使用は、MVC名前空間を使用したXMLによるものであれJavaコードによるものであれSpring MVCのコンフィギュレーションにおいてデフォルトで有効になっており、使用したくないという場合には明示的に設定変更をする必要があります。
メソッドの引数として使用できる型
次のものはメソッドの引数として使用可能です:
- (サーブレットAPIが提供する)リクエスト・オブジェクトとレスポンス・オブジェクト。特定のリクエストもしくはレスポンスの型を指定してください──例えば、
ServletRequest
もしくはHttpServletRequest
。 - (サーブレットAPIが提供する)セッション・オブジェクト。型は
HttpSession
。この型の引数を使用する場合、対応するセッションが実際に存在することが条件となります。ゆえに、この引数にnull
が設定されることは決してありません。
- サーブレットの環境によっては、セッションへのアクセスはスレッド・セーフでないことがあります。1つのセッションに複数のリクエストが並列アクセスする可能性がある場合、
RequestMappingHandlerAdapter
の"synchronizeOnSession"フラグに"true"を設定することを検討してみてください。 org.springframework.web.context.request.WebRequest
とorg.springframework.web.context.request.NativeWebRequest
。これらのオブジェクトを通じて、ネイティブのサーブレットAPIに縛られることなく、リクエストやセッションの属性、みならずリクエスト・パラメータにもアクセスできるようになります。java.util.Locale
は処理対象リクエストのロケールへのアクセスを提供します。ロケールは使用可能なうちでもっとも曖昧性の低いロケール・リゾルバ──実際上サーブレット環境で設定されているLocaleResolver
により決定されたものです。java.io.InputStream
やjava.io.Reader
はリクエストのコンテンツへのアクセスを提供します。サーブレットAPIにより提供されたそのままのInputStreamもしくはReaderオブジェクトです。java.io.OutputStream
やjava.io.Writer
はレスポンスのコンテンツを生成することを可能にします。サーブレットAPIにより提供されたそのままのOutputStreamもしくはWriterオブジェクトです。org.springframework.http.HttpMethod
はHTTPリクエストのメソッドを示します。java.security.Principal
は認証されたユーザ情報を内包したオブジェクトです。@PathVariable
アノテーションが付与された引数は、URIテンプレート変数へのアクセスを提供します。「URIテンプレート」のセクションを参照してください。@MatrixVariable
アノテーションが付与された引数は、URIパス断片に含まれる名前−値ペアへのアクセスを提供します。「マトリクス変数」のセクションを参照してください。@RequestParam
アノテーションが付与された引数は、特定のサーブレット・リクエスト・パラメータへのアクセスを提供します。パラメータの値は、アノテーションが付与されたメソッド引数の型に変換されます。「@RequestParamアノテーションが付与されたメソッド引数に対するリクエスト・パラメータの割り当て」〔 “Binding request parameters to method parameters with @RequestParam”〕のセクションを参照してください。@RequestHeader
アノテーションが付与された引数は、特定のHTTPリクエスト・ヘッダへのアクセスを提供します。ヘッダの値は、アノテーションが付与されたメソッド引数の型に変換されます。@RequestBody
アノテーションが付与された引数は、HTTPリクエスト本文へのアクセスを提供します。リクエスト本文の値はHttpMessageConverters
によってメソッド引数の型に変換されます。「@RequestBodyアノテーションによりリクエスト本文をマッピングする」〔“Mapping the request body with the @RequestBody annotation”〕のセクションを参照してください。@RequestPart
アノテーションが付与された引数は、"multipart/form-data"リクエストのコンテンツへのアクセスを提供します。「プログラムからのファイル・アップロードを処理する」(17.10.5)と「Springによるマルチパート(ファイル・アップロード)のサポート」(17.10)を参照してください。HttpEntity<?>
型の引数は、サーブレット・リクエストに格納されたHTTPヘッダとコンテンツへのアクセスを提供します。リクエストのストリームは、HttpMessageConverters
を使用してエンティティの本文に変換されます。「HttpEntityを使用する」のセクションを参照してください。java.util.Map
、org.springframework.ui.Model
、org.springframework.ui.ModelMap
はビューに提供される暗黙のモデルを強化するものです。org.springframework.web.servlet.mvc.support.RedirectAttributes
はリダイレクトの際に使用する属性のセットを明確に指定するため、またフラッシュ属性(サーバ側に一時的に保存され、リダイレクト後のリクエストで使用可能)を追加するために使用します。ハンドラ・メソッドが"redirect:" という接頭辞付きのビュー名やRedirectView
を返す場合、暗黙のモデルの代わりにRedirectAttributes
が使用されます。- コマンド・オブジェクトもしくはフォーム・オブジェクト。リクエスト・パラメータとJavaBeansプロパティ(セッター・メソッドにより設定される)やフィールド(直接設定される)を結びつけるこれらのオブジェクトには、
@InitBinder
メソッドおよび(あるいは)HandlerAdapterの設定に基づくカスタマイズ可能な型変換の機能が提供されます。RequestMappingHandlerAdapter
のプロパティwebBindingInitializer
のJavadocを参照してください。これら、それ自身のバリデーション結果を伴うコマンド・オブジェクトは、モデルの属性として呈示されます。その属性名はデフォルトではコマンド・クラスのクラス名から導出され、例えば"some.package.OrderAddress"という型のオブジェクトであれば"orderAddress" という属性名になります。ModelAttribute
アノテーションをメソッド引数に付与することで、モデルの当該属性の名称をカスタマイズできます。 org.springframework.validation.Errors
とorg.springframework.validation.BindingResult
には先行する(メソッド引数上、直前の)コマンド・オブジェクトもしくはフォーム・オブジェクトのバリデーション結果が格納されます。org.springframework.web.bind.support.SessionStatus
はフォームの処理が完了したことを示すのに使用します。これにより型レベルの@SessionAttributes
アノテーションで指定されているセッション属性がクリーンアップされます。org.springframework.web.util.UriComponentsBuilder
は、処理対象のリクエストのホスト、ポート、スキーム、コンテキスト・パス、そしてサーブレット・マッピングのリテラル部分に基づいてURLを構成するビルダ・オブジェクトです。
Errors
とBindingResult
型の引数は、モデル・オブジェクトのすぐ後ろに配置する必要があります。メソッドのシグネチャ上、モデル・オブジェクトは複数設定することができますが、この場合Springはそれぞれのモデルに対して個別の BindingResult
インスタンスを生成します。したがってこのあと示す例は正しく動作しません:
BindingResult
と@ModelAttribute
の順序の不備
@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }
Model
型の引数がPet
と BindingResult
の間にあります。正しく機能するようにするには、次のように並び替える必要があります:
@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }
メソッドの戻り値として指定可能な型
戻り値の型として次のものが指定できます:
ModelAndView
オブジェクト。〔訳注:ビューの情報だけでなく、コマンド・オブジェクトと@ModelAttribute
アノテーションが参照するアクセサ・メソッドが返す結果値により暗黙の値設定を施されたモデル・オブジェクトをともなうオブジェクトです。Model
オブジェクト。ビュー名はRequestToViewNameTranslator
により暗黙のうちに決定されます。モデル・オブジェクトは、コマンド・オブジェクトと@ModelAttribute
アノテーションが参照するアクセサ・メソッドが返す結果値により暗黙の値設定を施されます。Map
オブジェクト。モデルを表わすオブジェクトです。コマンド・オブジェクトと@ModelAttribute
アノテーションが参照するアクセサ・メソッドが返す結果値により暗黙の値設定を施されます。View
オブジェクト。ビューに渡されるモデルは、コマンド・オブジェクトと@ModelAttribute
アノテーションが参照するアクセサ・メソッドが返す結果値により暗黙のうちに決定されます。ハンドラ・メソッドは、引数としてModel
型の引数を通じて、プログラム・コードから値を追加することもできます(前述)。String
型の値。この値はビューの論理名として処理されます。ビューに渡されるモデルは、コマンド・オブジェクトと@ModelAttribute
アノテーションが参照するアクセサ・メソッドが返す結果値により暗黙のうちに決定されます。ハンドラ・メソッドは、引数としてModel
型の引数を通じて、プログラム・コードから値を追加することもできます(前述)。void
。メソッドが(引数としてServletResponse
やHttpServletResponse
をとり、レスポンス内容を直接書き出すことで)それ自身でレスポンスを処理する場合や、ビュー名がRequestToViewNameTranslator
により暗黙のうちに決定される場合(ただしハンドラ・メソッドの引数にレスポンスが存在しない場合)。@ResponseBody
アノテーションを付与されたメソッドの戻り値はHTTPレスポンスの本文に出力されます。戻り値はHttpMessageConverters
を使ってメソッドの戻り値として宣言された型に変換されます。「@ResponseBodyアノテーションでレスポンス本文をマッピングする」のセクションを参照してください。HttpEntity<?>
とResponseEntity<?>
オブジェクトはサーブレット・レスポンスのHTTPヘッダと本文へのアクセスを提供します。エンティティの本文はHttpMessageConverters
によりHTTPレスポンスのストリームに変換されます。「HttpEntityを使用する」のセクションを参照してください。HttpHeaders
オブジェクトは本文のないレスポンスを返すのに使用します。Callable<?>
はSpring MVCにより管理されたスレッド上で、アプリケーションが必要と判断したタイミングで値を生成して返すのに使用できます。DeferredResult<?>
はそれ自身により選択されたスレッド上で、アプリケーションが必要と判断したタイミングで値を生成して返すのに使用できます。ListenableFuture<?>
はそれ自身により選択されたスレッド上で、アプリケーションが必要と判断したタイミングで値を生成して返すのに使用できます。- その他の戻り値型はいずれも、ビューに対して提供されるモデルの一属性値とみなされます。属性の名称にはメソッド・レベルの
@ModelAttribute
で指定されたもの(あるいは、戻り値型名から導出されたデフォルトの属性名)が使用されます。モデルはまた、コマンド・オブジェクトと@ModelAttribute
アノテーションが付与されたオブジェクトのアクセサ・メソッドが返す値により暗黙のうちに値設定されます。
@RequestParam
によりリクエスト・パラメータをメソッド引数に結びつける
リクエスト・パラメータをコントローラのメソッド引数に結びつけるには@RequestParam
アノテーションを使用します。
次のコード断片はその使用方法を示しています:
@Controller @RequestMapping("/pets") @SessionAttributes("pet") public class EditPetForm { // ... @RequestMapping(method = RequestMethod.GET) public String setupForm(@RequestParam("petId") int petId, ModelMap model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute("pet", pet); return "petForm"; } // ... }
アノテーションを付与されたパラメータはデフォルトでは設定必須の値となります。しかしアノテーションの required
属性にfalse
を設定することでパラメータを設定任意の値となります(例えば、@RequestParam(value="id", required=false)
)。
対象のメソッド引数がString
型以外である場合は型変換が自動で適用されます。「メソッド引数と型変換」のセクションを参照してください。
@RequestBody アノテーションによりリクエスト本文をマッピングする
@RequestBody
メソッド引数アノテーションは、対象の引数がHTTPリクエスト本文を設定されるべきものであることを示すのに使用されます。例えば:
@RequestMapping(value = "/something", method = RequestMethod.PUT) public void handle(@RequestBody String body, Writer writer) throws IOException { writer.write(body); }
リクエスト本文はHttpMessageConverter
によってメソッド引数の型に変換されます。HttpMessageConverter
はHTTPリクエスト・メッセージを他のオブジェクトへ、また他のオブジェクトをHTTPレスポンスへとそれぞれ変換する役目を負います。RequestMappingHandlerAdapter
は@RequestBody
アノテーションを処理するにあたり、以下のデフォルトの HttpMessageConverters
を使用します:
ByteArrayHttpMessageConverter
はバイト配列の変換を行います。StringHttpMessageConverter
は文字列型の変換を行います。FormHttpMessageConverter
はフォーム・データとMultiValueMap< String, String>
の間の変換を行います。SourceHttpMessageConverter
はjavax.xml.transform.Source
との間の変換を行います。
これらのコンバーターについて、より詳しい情報は「メッセージ・コンバーター」を参照してください(22.10.2)。またもしXMLもしくはJavaコードによるコンフィギュレーションを使用しているのであれば、デフォルトで非常に多くのコンバーターが用意されていることを知っておいたほうがいいでしょう。これについて詳しくは、セクション17.16.1の「JavaコードもしくはMVC名前空間を使用したXMLによる設定の有効化」を参照してください。
XMLの読み込み/書き込みをするには、 org.springframework.oxm
パッケージに含まれる Marshaller
とUnmarshaller
の特定の実装とともに、MarshallingHttpMessageConverter
の設定を行う必要があります。下記の例ではこれらを直接設定する方法を示しています〔訳注:ここで「直接」と表現されているのはSpringの通常のApplicationContext.xmlによる設定のことです〕。あなたのアプリケーションが、MVCの名前空間を使用したXMLやJavaコードにより設定を施されている場合は、セクション17.16.1の「JavaコードもしくはMVC名前空間を使用したXMLによる設定の有効化」を参照してください。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <util:list id="beanList"> <ref bean="stringHttpMessageConverter"/> <ref bean="marshallingHttpMessageConverter"/> </util:list> </property </bean> <bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter"/> <bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"> <property name="marshaller" ref="castorMarshaller" /> <property name="unmarshaller" ref="castorMarshaller" /> </bean> <bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>
@RequestBody
が付与されたハンドラ・メソッドの引数には、@Valid
アノテーションを付与することで、コンフィギュレーションで定義したValidator
によるバリデーションの対象にすることができます。MVC名前空間を使ったXMLによるコンフィギュレーションやJavaコードによるコンフィギュレーションの機能を使う場合、JSR-303準拠のバリデータ実装がクラスパス上にあるものとして自動で設定が行われます。
Errors
型引数は、ちょうど@ModelAttribute
付与されたメソッド引数とともに使用したときと同じように、エラーの有無を確認するのに使用できます。そのような引数が宣言されていない場合は、代わりに MethodArgumentNotValidException
例外が発生します。この例外は DefaultHandlerExceptionResolver
により処理され、クライアントに対して400
コードが返されます。
[NOTE] JavaコードもしくはMVC名前空間を使用したXMLによるメッセージ・コンバーターとバリデータのコンフィギュレーションについては、セッション17.16.1の「JavaコードもしくはMVC名前空間を使用したXMLによる設定の有効化」も参照してください。
@ResponseBodyアノテーションによりレスポンス本文をマッピングする
@ResponseBody
アノテーションは@RequestBody
と似ています。このアノテーションはメソッドに対して設定することができ、当該のメソッドの戻り値型がHTTPレスポンス本文に直接書き込まれるべきものであること(したがってそれがモデルの要素となることも、ビュー名として扱われることも、いずれもないこと)を示します。例えば:
@RequestMapping(value = "/something", method = RequestMethod.PUT) @ResponseBody public String helloWorld() { return "Hello World"; }
上記の例では Hello World
というテキストがHTTPレスポンスのストリームに書き出されることになります。
@RequestBody
についてと同様、Springはメソッドが返したオブジェクトをレスポンス本文に変換するのに HttpMessageConverter
を使用します。これらのコンバーターについて詳しくは、前のセクションと「メッセージ・コンバーター」を参照してください(22.10.2)。
@RestController
アノテーションでRESTコントローラを作成する
JSON、XMLのそして独自のメディア・タイプのコンテンツを提供するために、REST APIを実装したコントローラが必要になることがしばしばあります。こうした用途のために、@RequestMapping
メソッドを@ResponseBody
アノテーションとともに定義するのではなく、コントローラ・クラスを@RestController
アノテーションにより定義する方法が使えます。
@RestController
はステレオタイプ・アノテーションで、@ResponseBody
と@Controller
を組み合わせたものです。のみならず、このアノテーションはコントローラにより多くの機能を付与しており、さらにSpringの将来のリリースにおいて新たな機能が提供されるかもしれません。
通常の@Controller
と同じく、@RestController
もまた@ControllerAdvice
ビーンによる支援対象となることもあります。「@ControllerAdviceアノテーションによりコントローラをAOP対象にする」〔“Advising controllers with the @ControllerAdvice annotation”〕というセクションを参照してください。
HttpEntityを使う
HttpEntity
は@RequestBody
と @ResponseBody
に似ています。リクエスト本文とレスポンス本文へのアクセスを提供するほか、このHttpEntity
(そしてレスポンスに特化したサブクラスである ResponseEntity
)はリクエスト・ヘッダとレスポンス・ヘッダへのアクセスを提供します。例えば次のように:
@RequestMapping("/something") public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException { String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader")); byte[] requestBody = requestEntity.getBody(); // リクエストのヘッダと本文に関連する何かを実行 HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.set("MyResponseHeader", "MyValue"); return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED); }
上記の例では MyRequestHeader
リクエスト・ヘッダの値を取得し、本文についてはバイト配列として読み込んでいます。その後、レスポンスに MyResponseHeader
を追加し、レスポンスのストリームに「Hello World」と書き込んでいます。最後に、レスポンス・ステータス・コードに201(Created)を設定しています。
@RequestBody
や @ResponseBody
と同様、Springはオブジェクトをリクエストやレスポンスのストリームとの間で変換するに際してHttpMessageConverter
を使用します。これらのコンバーターについて詳しくは、前のセクションと「メッセージ・コンバーター」を参照してください(22.10.2)。
@ModelAttributeアノテーションをメソッドに対して使用する
@ModelAttribute
アノテーションはメソッド・レベルとメソッド引数レベルのそれぞれで使用できます。このセクションではメソッド・レベルでの使用について、そして次のセクションではメソッド引数レベルでの使用について説明します。
メソッドに対して付与された@ModelAttribute
は、当該メソッドが1つもしくはそれ以上の数のモデル属性を追加するものであることを示します。これらのメソッドは@RequestMapping
が付与されたメソッドと同じ型の引数を取ることができますが、リクエストと直接マッピングすることはできません。コントローラ上に宣言された@ModelAttribute
メソッドは同じコントローラ上の@RequestMapping
メソッドが実行される前に呼び出されます。2例をあげましょう:
// 1つの属性を追加する // メソッドの戻り値はモデルの属性として"account"という名前で追加される。 // この挙動は@ModelAttribute("myAccount")というふうな指定によりカスタマイズ可能。 @ModelAttribute public Account addAccount(@RequestParam String number) { return accountManager.findAccount(number); } // 複数の属性を追加する @ModelAttribute public void populateModel(@RequestParam String number, Model model) { model.addAttribute(accountManager.findAccount(number)); // その他の属性についても処理・・・ }
@ModelAttribute
メソッドは、ステータスやペットの種類を要素とするドロップダウンリストのデータを初期化したり、HTMLフォームのデータを表わすAccountのようなコマンド・オブジェクトを取得したりする共通処理として、モデルにデータを詰め込むのに使用されています。後者については次のセクションで解説します。
@ModelAttribute
メソッドに2つのスタイルがあることに注意してください。最初の例では、メソッドはその戻り値により暗黙のうちにモデルに属性を追加しています。その次の例では、メソッドはModel
を引数にとり、そのモデルに属性をいくつか追加しています。2つのスタイルは必要に応じて使い分けてください。
コントローラはいくつでも@ModelAttribute
メソッドを持つことができます。これらはいずれも同一コントローラ上の @RequestMapping
メソッドに先立って実行されます。
@ModelAttribute
メソッドは @ControllerAdvice
アノテーションが付与されたクラス上にも定義できます。これらのメソッドは複数のコントローラに対して適用されることになります。詳細については、「@ControllerAdviceアノテーションによりコントローラをAOP対象にする」〔“Advising controllers with the @ControllerAdvice annotation”〕というセクションを参照してください。
[NOTE] モデルの属性名を明示的に指定しない場合どうなるのでしょうか? この場合、モデルの属性にはその型名を元にしたデフォルトの名前が割り当てられます。例えばメソッドがAccount
型のオブジェクトを返す場合、デフォルトの名前は"account"となります。@ModelAttribute
アノテーションの値を通じてこの挙動は変更ができます。Model
に直接属性を追加する場合、対象とする型ごとにオーバーロードされたaddAttribute(..)
メソッドを使用します。属性名を明示的に指定することもしないこともできます。
@ModelAttribute
アノテーションは@RequestMapping
メソッドに対して指定することもできます。この場合、 @RequestMapping
メソッドの戻り値は、ビュー名ではなくモデルに追加される属性値として解釈されます。戻り値がvoidであるメソッドの場合と同様、ビュー名は規約に基づき自動導出されます。これについては、セクション17.13.3の「ビューとRequestToViewNameTranslator」〔“The View - RequestToViewNameTranslator”〕を参照してください。
@ModelAttributeをメソッドの引数に対して使用する
前述のとおり、@ModelAttribute
はメソッド・レベルでもメソッド引数レベルでも使用できます。このセクションではメソッド引数レベルでの使用について見てみましょう。
メソッド引数に付与された@ModelAttribute
は、引数の値がモデルから取得されるべきであることを示します。もしその引数に対応する値がモデル内に存在しなければ、まずその値が初期化され、そののちモデルに追加されます。ひとたびモデル内に値が追加されると、それぞれのメソッド引数にはすべてのリクエスト・パラメータの中から名前の一致するものが割り当てられます。これはSpring MVCの用語で「データ・バインディング」と呼ばれているもので、あなた自身がフォームのフィールドの値を個別に読み取るコードを書く手間を省いてくれる、非常に便利な仕組みです。
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute Pet pet) { }
上記の例で、Petインスタンスはどこからやってくるのでしょうか? これにはいくつかの候補があります:
@SessionAttributes
によりあらかじめモデルに設定されているインスタンス──これについては「 @SessionAttributesを使ってモデルの属性値をHTTPセッションに保存する」というセクションを参照してください。- 同一コントローラ上の
@ModelAttribute
メソッドによりによりあらかじめモデルに設定されているインスタンス──これについては1つ前のセクションですでに説明しました。 - URIテンプレート変数と型コンバーターに基づき取得されるインスタンス(後述)。
- デフォルト・コンストラクタにより初期化されたインスタンス。
@ModelAttribute
メソッドは何かしらのデータベースから属性値を取得するための一般的な方法です。それらの値には@SessionAttributes
を使用することで、リクエストを跨いで保管されるものも含まれるでしょう。このメソッドはまたURIテンプレート変数と型コンバーターを使用して属性値を取得するための簡便な手段でもあります。次に例を示します:
>|java|
@RequestMapping(value="/accounts/{account}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) {
}
|
この例の中で、モデルの属性の名称("account")はURIテンプレート変数の名前に一致しています。 Converter<String, Account>
を登録することで、String
型のデータをAccount
型のインスタンスに変換できます。上記の例は、 @ModelAttribute
を使用しなくても機能します。
データ・バインディングについてもう一歩踏み込んでみましょう。 WebDataBinder
クラスはリクエスト・パラメータ名──クエリ文字列とフォーム・フィールドを含む──とモデル属性名とを照合します。そして一致したフィールドは、必要に応じて(文字列から対象のフィールドの型へ)型変換を施されたあとで、モデルの属性の値として設定されます。データ・バインディングとバリデーションのメカニズムについては第7章の「バリデーション、データ・バインディング、そして型変換」で解説されています。コントローラ・レベルのデータ・バインディング処理をカスタマイズ方法については「WebDataBinderの初期化をカスタマイズする」というセクションを参照してください。
データ・バインディングの結果として、必須のフィールドが未設定のままとなったり型変換に失敗したりといったエラーが発生することがあります。 BindingResult
型の引数を@ModelAttribute
を付与した引数の直後に配置することでこうしたエラーをチェックすることができます:
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) { return "petForm"; } // ... }
BindingResult
によりエラー発生の有無を確認できます。この例では、エラーがあった場合、よくあるように同じフォームを再表示しています。Springの
データ・バインディングについては、同じ BindingResult
オブジェクト──データ・バインディングの際のエラーを記録するのに使用される──をカスタム・バリデータに渡してバリデーションを行うこともできます。このオブジェクトにより、データ・バインディングとバリデーションに際して発生したエラーの情報を集積して、ユーザ側に結果を返すことができるようになります:
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { new PetValidator().validate(pet, result); if (result.hasErrors()) { return "petForm"; } // ... }
カスタム・バリデータを使う以外に、JSR-303の@Valid
アノテーションにより追加され、自動実行されるバリデーションも使用できます:
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) { return "petForm"; } // ... }
バリデーションの設定と使用についてより詳しくは、セクション7.8の「Springのバリデーション」と第7章「バリデーション、データ・バインディング、そして型変換」を参照してください。
@SessionAttributesを使ってモデルの属性値をHTTPセッションに保存する
型レベルの@SessionAttributes
アノテーションにより、特定のハンドラにより使用されるセッション属性を定義できます。これは一般にモデル属性名のリストやモデル属性の型のリストのかたちで宣言され、セッションもしくは何らかの簡単なストレージ──後続のリクエストとの間でフォームの情報を保持するビーンとして機能する──の中に透過的に格納されます。
次のコード断片は、モデル属性の名称を指定してこのアノテーションを使用する方法を示すものです:
@Controller @RequestMapping("/editPet.do") @SessionAttributes("pet") public class EditPetForm { // ... }
リダイレクト指定とフラッシュ属性
デフォルトで、すべてのモデル属性はリダイレクトURLのURIテンプレート変数として利用可能なものとみなされます。そして、残りの属性値のうちプリミティブ型とプリミティブ型のコレクション/配列は、自動的にクエリ文字列としてリダイレクトURLの末尾に追加されます。
とはいえ、モデルには画面表示に使用される目的で追加されたオリジナルな〔訳注:リクエスト・パラメータに直接由来しない〕属性も含まれていることがあります。リダイレクトの処理の過程で、これらの属性についてきっちり制御をするため、@RequestMapping
メソッドは RedirectAttributes
型の引数を宣言し、この引数が参照するオブジェクトに RedirectView
で使用するための属性を追加することができます。コントローラ・メソッドがリダイレクトを行うとき、 RedirectAttributes
に格納された情報が使用されます。このオブジェクトを使用しない場合、デフォルトのModel
が使用されることになります。
RequestMappingHandlerAdapter
は"ignoreDefaultModelOnRedirect"
というフラグ値を提供しています。これにより、デフォルトのModel
に格納された情報がリダイレクトの際に使用されないよう指定することができます。ただしコントローラ・メソッドは RedirectAttributes
型の属性を宣言しなくてはなりません。宣言しない場合、 RedirectView
には何も渡されないことになります。MVC名前空間を使ったXMLによるコンフィギュレーションでも、Javaコードによるコンフィギュレーションでも、このフラグは後方互換性のためにfalse
となっています。しかし新しいアプリケーションを開発している場合には、この値にtrue
を設定することが推奨されています。
RedirectAttributes
インターフェースはフラッシュ属性の追加にも使用できます。最終的にはリダイレクトURLに組み込まれる他のリダイレクト属性とちがい、フラッシュ属性はHTTPセッションに保存されます(したがってまたそれらはURLに現れません)。リダイレクトURLに対応する側のコントローラのモデルは、フラッシュ属性を自動的に取得したあとそれらをセッション上から削除します。Spring MVCにおけるフラッシュ属性の一般的サポートの概要については、セクション17.6「フラッシュ属性を使用する」を参照してください。
"application/x-www-form-urlencoded"データを処理する
一つ前のセクションでは、ブラウザ・クライアントからのフォーム送信リクエストを処理するため @ModelAttribute
を使用する方法を確認しました。しかしこの同じアノテーションは非ブラウザ・クライアントからのリクエストについても同様に使用が推奨されています。ただしHTTP PUTリクエストを処理するにあたっては注目すべき差異もあります。ブラウザはフォーム・データをHTTP GETメソッドもしくはPOSTメソッドにより送信することができます。非ブラウザ・クライアントはこれらに加えてHTTP PUTメソッドによるフォーム・データの送信もできます。これはひとつの問題です。なぜならサーブレット仕様では、HTTP POSTについては ServletRequest.getParameter*()
系のメソッドによるフォーム・フィールド・アクセスのサポートが要求されていますが、HTTP PUTについてはそうではないからです。
HTTP PUTリクエストとPATCHリクエストとをサポートするため、 spring-web
モジュールはHttpPutFormContentFilter
を提供しています。このフィルタは web.xml
で設定できます:
filter> <filter-name>httpPutFormFilter</filter-name> <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class> </filter> <filter-mapping> <filter-name>httpPutFormFilter</filter-name> <servlet-name>dispatcherServlet</servlet-name> </filter-mapping> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet>
上記のフィルタはコンテント・タイプが application/x-www-form-urlencoded
であるHTTP PUTリクエストとPATCHリクエストをインターセプトし、リクエスト本文からフォーム情報を読み取り、 ServletRequest
でラップすることで、 ServletRequest.getParameter*()
系メソッドによりそれらのデータが利用できるようします。
[NOTE] HttpPutFormContentFilter
にリクエスト本文を処理させるため、コンテント・タイプ application/x-www-form-urlencoded
のための他のコンバーターを使ってPUTやPATCHのURLを処理させるような設定をしてはいけません。これには@RequestBody MultiValueMap<String, String>
やHttpEntity<MultiValueMap<String, String>>
が含まれます。
@CookieValue アノテーションでクッキー値をマッピングする
@CookieValue
アノテーションを使うと、HTTPクッキーの値をメソッド引数に割り当てることができます。
HTTPリクエストとともに次のクッキーを受け取った時のことを考えてみましょう:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
次のサンプル・コードはこの JSESSIONID
というクッキー値をどのようにして取得するかを示しています:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) { //... }
対象のメソッド引数型が文字列型でない場合、型変換が自動で行われます。「メソッド引数と型変換」というセクションを参照してください。
@RequestHeader アノテーションによりリクエスト・ヘッダをマッピングする
@RequestHeader
アノテーションを使うことで、リクエスト・ヘッダの値をメソッド引数に割り当てることができます。
次に示すのはサンプルのリクエスト・ヘッダです:
Host localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
次のサンプル・コードは、 Accept-Encoding
とKeep-Alive
の値を取得する方法を示しています:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { //... }
対象のメソッド引数型が文字列型でない場合、型変換が自動で行われます。「メソッド引数と型変換」というセクションを参照してください。
[NOTE] カンマ区切りの文字列を文字列もしくは他の型の配列/コレクションに変換する型変換の仕組みが組み込みでサポートされています。例えば、@RequestHeader("Accept")
というアノテーションが付与されたメソッド引数は、その型としてString
だけでなくString[]
やList<String>
も指定可能です。
メソッド引数と型変換
リクエスト・パラメータ、パス変数、リクエスト・ヘッダ、そしてクッキー値。これらのリクエスト情報から抽出された文字列型の値は、ときにより対象のメソッド引数やフィールド(例えば、@ModelAttribute
によりリクエスト・パラメータはフィールドに結び付けられます)の型にあわせて変換してやる必要がでてきます。対象の型がString
型でない場合、Springは自動で適切な方に変換を行います。int、long、Date、その他の簡単な構造の型はすべてサポートされています。加えて、この型変換のプロセスは、 WebDataBinder
(「WebDataBinderの初期化をカスタマイズする」というセクションを参照してください)やFormattingConversionService
(セクション7.6「Springによるフィールドの書式化」を参照してください)により Formatters
を登録することでカスタマイズ可能です。
WebDataBinderの初期化をカスタマイズする
SpringのWebDataBinder
に登録されたPropertyEditor実装によるリクエスト・パラメータのバインディングをカスタマイズするには、コントローラや@ControllerAdvice
クラスのメンバとして定義した@InitBinder
アノテーション付きメソッドを使用するか、オリジナルの WebBindingInitializer
を使用することで可能です。より詳細な情報については「@ControllerAdviceアノテーションによりコントローラをAOP対象にする」のセクションを参照してください。
@InitBinderでデータ・バインディングをカスタマイズする
@InitBinder
アノテーションをコントローラのメソッドに付与することで、当該コントローラ・クラスにおけるデータ・バインディングの方法を直接的に設定変更できます。@InitBinder
はそれが付与されているメソッドがWebDataBinder
──ハンドラ・メソッドが宣言しているコマンド・オブジェクト型やフォーム・オブジェクト型の引数に実際の値を割り当てる──を初期化するものであることを示します。
@InitBinder
メソッドは、@RequestMapping
がサポートするすべての引数形式──ただしコマンド/フォーム・オブジェクトを除く──を引数にとることができます。これらのメソッドは戻り値を決して返しません。したがっていつもvoid
と宣言されます。これらのメソッドの典型的な使用法としては、WebDataBinder
を WebRequest
やjava.util.Locale
とともに引数にとることで、文脈固有のプロパティ・エディタの登録を行うコードを実現するというものがあります。
次の例では、 CustomDateEditor
── java.util.Date
型のプロパティのために使用される──を設定するため@InitBinder
を使用しています。
@Controller public class MyFormController { @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } // ... }
独自のWebBindingInitializerを設定する
データ・バインディングの初期化処理を外部化するには、 WebBindingInitializer
インターフェースの独自の実装を用意して、 AnnotationMethodHandlerAdapter
のプロパティに設定します。これによりデフォルトの振る舞いが上書きされます。
次の例はPetClinicアプリケーションから抜粋したものですが、 WebBindingInitializer
インターフェースの独自実装である org.springframework.samples.petclinic.web.ClinicBindingInitializer
クラスを使用してデータ・バインディングの設定変更を行っています。この実装はPetClinicのコントローラ群のいくつかで必要とされるPropertyEditorを設定するためのものです。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="cacheSeconds" value="0" /> <property name="webBindingInitializer"> <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer" /> </property> </bean>
@InitBinder
メソッドは@ControllerAdvice
アノテーションが付与されたクラスでも宣言できます。この場合、AOP対象のすべてのコントローラがデータ・バインディングの設定変更の対象になります。またWebBindingInitializer
は代替手段を提供します。「@ControllerAdviceアノテーションによりコントローラをAOP対象にする」〔“Advising controllers with the @ControllerAdvice annotation”〕というセクションを参照してください。
Last-Modified コンテンツ・キャッシュを可能にするレスポンス・ヘッダのサポート
@RequestMapping
メソッドを実装するに際して、コンテンツ・キャッシュを手助けするために、サーブレットAPIの getLastModified
メソッドに頼ることで'Last-Modified'
ヘッダ付きのレスポンスの生成をサポートしようと思うかもしれません。そのためには、lastModifiedのlong
値の計算し、計算結果とリクエスト・ヘッダ'If-Modified-Since'
の比較し、その比較結果次第ではステータス・コード304(Not Modified)のレスポンスを返す必要があります。これらの仕事をハンドラ・メソッドで実装するには次のようにします:
@RequestMapping public String myHandleMethod(WebRequest webRequest, Model model) { long lastModified = // 1. アプリケーション固有の計算 if (request.checkNotModified(lastModified)) { // 2. 処理を抜けるためのショートカット。リクエスト処理を続ける必要のないことを示す return null; } // 3. さもなくばリクエストの処理を続行して、コンテンツを準備する model.addAttribute(...); return "myViewName"; }
この例の中には重要な事項が2点あります: request.checkNotModified(lastModified)
メソッドの呼び出しと、null
の返却です。前者はtrue
を返す際、それに先立ってレスポンスのステータス・コードを304に設定します。これと合わせて、後者ではSpring MVCに対してこれ以上のリクエスト処理を行わないようさせています。
@ControllerAdviceアノテーションによりコントローラをAOP対象にする
@ControllerAdvice
アノテーションが付与されたクラスはクラスパス上で自動検知されます。この仕組はMVC名前空間を使用したXMLによるコンフィギュレーションでも、Javaコードによるそれでも、自動で有効化されます。
Classes annotated with @ControllerAdvice
アノテーションを付与されたクラスのメソッドには、@ExceptionHandler
や@InitBinder
、@ModelAttribute
といったアノテーションを付与できます。これらのメソッドは、AOP対象のコントローラ同士の階層構造にかかわりなく、階層構造横断的にそれらのコントローラで宣言された @RequestMapping
メソッドに適用されます。
@ControllerAdvice
アノテーションの属性の設定次第では、コントローラ群のなかの特定のサブセットを対象とするようにすることもできます:
// @RestControllerが付与されたすべてのコントローラを対象とする @ControllerAdvice(annotations = RestController.class) public class AnnotationAdvice {} // 特定のパッケージに含まれるすべてのコントローラを対象とする @ControllerAdvice("org.example.controllers") public class BasePackageAdvice {} // 特定のクラス名を指定してAOP対象とする @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) public class AssignableTypesAdvice {}
より詳しい情報については、@ControllerAdvice
のJavadocを参照してください。
Jacksonによるシリアライズを行うビューのサポート
状況次第では、HTTPレスポンス本文に何かしらのオブジェクトをシリアライズした結果を出力するフィルタがあると便利なことがあります。このような状況での便宜を提供するために、Spring MVCはJacksonのシリアライゼーション・ビューによるレンダリングを組み込みでサポートしています。
これを使用するのに必要なのは、@ResponseBody
アノテーションを付与されたコントローラ・メソッドかResponseEntity
を戻り値とするコントローラ・メソッドに、 @JsonView
アノテーションを付与することだけです。アノテーションにはJacksonビュー・クラスもしくはインターフェースを引数として指定します:
@RestController public class UserController { @RequestMapping(value = "/user", method = RequestMethod.GET) @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; } }
[NOTE] @JsonView
アノテーションでは引数に2つ以上のクラスを指定することも可能ですが、コントローラ・メソッドとともに使用する場合には1つしか指定できない点に注意してください。もし複数のJacksonビューが必要な場合は、複合インターフェースの利用を検討してください。
コントローラの戻り値がビュー解決に使用されるものである場合は、Jacksonシリアライゼーション・ビューのクラスはモデルの属性として登録します:
@Controller public class UserController extends AbstractController { @RequestMapping(value = "/user", method = RequestMethod.GET) public String getUser(Model model) { model.addAttribute("user", new User("eric", "7!jd#h23")); model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class); return "userView"; } }
Jacksonによる JSONP のサポート
@ResponseBody
やResponseEntity
が付与されたメソッドでJSONPをサポートする場合、AbstractJsonpResponseBodyAdvice
を拡張して@ControllerAdvice
ビーンを宣言します。以下の例で示されるように、コンストラクタ引数はJSONPのクエリ・パラメータ名(複数の場合もある)です:
@ControllerAdvice public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice { public JsonpAdvice() { super("callback"); } }
コントローラの戻り値がビュー解決に使用されるものである場合は、リクエストがjsonp
やcallback
という名前のクエリ・パラメータを持っていることでJSONPが自動で有効化されます。これらJSONP有効化のトリガーとなる名前については jsonpParameterNames
プロパティでカスタマイズできます。
* * *
続きは次の投稿で公開予定です。
原典は、Springフレームワーク(本体)のリファレンス・マニュアルである"Spring Framework Reference Documentation"の第5部"The Web"の第17章"Web MVC framework"の第3節(バージョン4.1.1.RELEASE現在)です。