@ModelAttributeとBindingResultの順序を正しく設定しないとリクエストがマッピングされない件 – Springバッドノウハウ

2013.11.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

渡辺です。札幌は既に冬モードとなり、今週末には雪の予報も出ています。そういった事はまったく関係なく、今日はSpringの小ネタでいってみましょう。

まあ、ドはまりしたワケですが、中々検索しても原因が見つかりませんでした。今後は同様の問題で困っている人が、日本語情報でヒットするようにとブログに残しておきます。

状況

あるアプリケーションで更新機能で次のようなアクションメソッドを定義していました。

@RequestMapping(value = "/item/new", method = RequestMethod.POST)
public String onPost(
    @Valid @ModelAttribute("form") ItemForm form, 
    BindingResult result) {
    if (result.hasErrors()) {
        return "item-new";
    }
    service.save(form.newItem());
    return "redirect:/item";
}

なんのこともない、良くある更新系処理のPRG(Post Redirect Get)的メソッドです。

今回、この機能を修正するにあたり、Modelに追加のアトリビュートを設定する必要が生まれました。今回のプロジェクトではアクションメソッドの戻り値はString型と決めていたので、Modelをメソッドに追加し、そこにエラーがあった場合はアトリビュートを詰めることになります。なお、@ModelAttributeを使った独立したメソッドにする方法もありますが、今回はformのデータを利用しなければならないため使えません。

というわけで、次のように修正しました。

@RequestMapping(value = "/item/new", method = RequestMethod.POST) public String onPost( @Valid @ModelAttribute("form") ItemForm form, Model model, // <- NEW! BindingResult result) { if (result.hasErrors()) { model.addAttribute( ... ); // <- NEW! return "item-new"; } service.save(form.newItem()); return "redirect:/item"; } [/java]

すると、POSTをしてもonPostがコールされず、404となってしまったのです...。

@ModelAttributeやBindingResultを定義したメソッドに、Modelオブジェクトを追加すると、期待通りにリクエストがマッピングされないのです。

原因と解決方法

散々調べた結果、解決方法はSpringのドキュメントに、チラっと書いてありました。

The Errors or BindingResult parameters have to follow the model object that is being bound immediately as the method signature might have more that one model object and Spring will create a separate BindingResult instance for each of them so the following sample won't work:

つまり、パラメータにBindingResultを定義する場合は、対応するモデルオブジェクトの直後に定義しなければならないってことです。言い換えれば、引数の順番が悪い、と。

@RequestMapping(value = "/item/new", method = RequestMethod.POST) public String onPost( @Valid @ModelAttribute("form") ItemForm form, BindingResult result, Model model // <- NEW! ) { if (result.hasErrors()) { model.addAttribute( ... ); // <- NEW! return "item-new"; } service.save(form.newItem()); return "redirect:/item"; } [/java]

これで期待通りに動作します。

ちょっとフレームワーク側の挙動を考えると「まあ・・確かにね・・・」と納得はできるんですが、突然の404で何が問題なのかが解らず、途方に暮れてしまったワケです。この手のフレームワークの制約やルールに起因した問題ははまると辛いですね。

というわけで、同じ問題でハマった人がこのブログにたどり着きますように...。