読者です 読者をやめる 読者になる 読者になる

M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Spring MVCのドキュメント「コントローラを実装する」を読む(2)

Java Spring

f:id:m12i:20141115150916p:plain

前回に引き続き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.WebRequestorg.springframework.web.context.request.NativeWebRequest。これらのオブジェクトを通じて、ネイティブのサーブレットAPIに縛られることなく、リクエストやセッションの属性、みならずリクエスト・パラメータにもアクセスできるようになります。
  • java.util.Localeは処理対象リクエストのロケールへのアクセスを提供します。ロケールは使用可能なうちでもっとも曖昧性の低いロケール・リゾルバ──実際上サーブレット環境で設定されている LocaleResolverにより決定されたものです。
  • java.io.InputStreamjava.io.Reader はリクエストのコンテンツへのアクセスを提供します。サーブレットAPIにより提供されたそのままのInputStreamもしくはReaderオブジェクトです。
  • java.io.OutputStreamjava.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.Maporg.springframework.ui.Modelorg.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.Errorsorg.springframework.validation.BindingResultには先行する(メソッド引数上、直前の)コマンド・オブジェクトもしくはフォーム・オブジェクトのバリデーション結果が格納されます。
  • org.springframework.web.bind.support.SessionStatusはフォームの処理が完了したことを示すのに使用します。これにより型レベルの@SessionAttributesアノテーションで指定されているセッション属性がクリーンアップされます。
  • org.springframework.web.util.UriComponentsBuilderは、処理対象のリクエストのホスト、ポート、スキーム、コンテキスト・パス、そしてサーブレットマッピングリテラル部分に基づいてURLを構成するビルダ・オブジェクトです。

ErrorsBindingResult型の引数は、モデル・オブジェクトのすぐ後ろに配置する必要があります。メソッドシグネチャ上、モデル・オブジェクトは複数設定することができますが、この場合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メソッドが(引数としてServletResponseHttpServletResponseをとり、レスポンス内容を直接書き出すことで)それ自身でレスポンスを処理する場合や、ビュー名が 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>の間の変換を行います。
  • SourceHttpMessageConverterjavax.xml.transform.Sourceとの間の変換を行います。

これらのコンバーターについて、より詳しい情報は「メッセージ・コンバーター」を参照してください(22.10.2)。またもしXMLもしくはJavaコードによるコンフィギュレーションを使用しているのであれば、デフォルトで非常に多くのコンバーターが用意されていることを知っておいたほうがいいでしょう。これについて詳しくは、セクション17.16.1の「JavaコードもしくはMVC名前空間を使用したXMLによる設定の有効化」を参照してください。

XMLの読み込み/書き込みをするには、 org.springframework.oxm パッケージに含まれる MarshallerUnmarshallerの特定の実装とともに、MarshallingHttpMessageConverterの設定を行う必要があります。下記の例ではこれらを直接設定する方法を示しています〔訳注:ここで「直接」と表現されているのはSpringの通常のApplicationContext.xmlによる設定のことです〕。あなたのアプリケーションが、MVC名前空間を使用したXMLJavaコードにより設定を施されている場合は、セクション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コントローラを作成する

JSONXMLのそして独自のメディア・タイプのコンテンツを提供するために、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-EncodingKeep-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 WebRequestjava.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 {}

より詳しい情報については、@ControllerAdviceJavadocを参照してください。

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 のサポート

@ResponseBodyResponseEntityが付与されたメソッドJSONPをサポートする場合、AbstractJsonpResponseBodyAdvice を拡張して@ControllerAdvice ビーンを宣言します。以下の例で示されるように、コンストラクタ引数はJSONPのクエリ・パラメータ名(複数の場合もある)です:

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

コントローラの戻り値がビュー解決に使用されるものである場合は、リクエストがjsonpcallbackという名前のクエリ・パラメータを持っていることでJSONPが自動で有効化されます。これらJSONP有効化のトリガーとなる名前については jsonpParameterNamesプロパティでカスタマイズできます。

          * * *

続きは次の投稿で公開予定です。

原典は、Springフレームワーク(本体)のリファレンス・マニュアルである"Spring Framework Reference Documentation"の第5部"The Web"の第17章"Web MVC framework"の第3節(バージョン4.1.1.RELEASE現在)です。


Spring関連の訳出記事まとめ - M12i.