リダイレクトして入力チェックエラーを表示する方法まとめ
- 検出した入力チェックエラーは BindResult に格納される
- リダイレクトするときには RedirectAttributes へBindingResultを格納する
- リダイレクトした先のControllerでModelを初期化している場合は、BindingResultのデフォルト名で格納すると初期化されてしまうので別の名前を格納すること
入力チェックエラーを検出するには
SpringMVC(SpringBoot)の入力チェック機能は、@Controllerのメソッドにて、入力値を格納するメソッドの引数に@Valid または @Validated を宣言すると有効になります。
入力チェックの結果は、入力チェックを行いたい項目の次に BindingResult を宣言すると、SpringMVCは入力チェックを行った結果を格納します。格納した結果は画面から取り出せます。
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/")
public class SampleController {
/**
* 入力項目を格納するフォームクラス
*/
@ModelAttribute
SampleForm sampleForm() {
return new SampleForm();
}
@PostMapping("")
public ModelAndView check(ModelAndView mnv, @ModelAttribute @Validated SampleForm sampleForm, BindingResult bindingResult) {
if (bindingResult.hasFieldErrors()) {
// エラーが発生ししたので入力画面へ戻る
mnv.setViewName("index");
return mnv;
}
....
return mnv;
}
}
このサンプルのControllerでは、画面からのパラメータを SampleForm クラスで受け取ります。パラメータの定義と入力チェックのルールを、SampleForm で行います。BindingResult にて入力チェックエラーがあった場合はすぐに画面を表示します。
入力チェックの定義
例えばこのFormクラスでは、画面から userName の名前で1つのパラメータを受け取ります。この userName は1文字から10文字までを許容し、そうではなかった場合にエラーとします。このときの入力チェックのルールを定義します。
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class SampleForm {
@Size(min = 1, max = 10, message = "名前は{min}~{max}文字で入力してください")
private String userName;
}
画面の実装は以下です。送信フォームと入力項目のあたりを抜き出します。
<div>
<label for="userName">ユーザー名</label>
<input type="text" id="userName" th:field="*{userName}">
<div th:errors="*{userName}"></div>
</div>
<!-- ボタン -->
<div>
<button type="reset">リセット</button>
<button type="submit">送信</button>
</div>
入力チェックエラーを検出したときには、th:errors でエラーメッセージを出力します。
ここまでのまとめ
- SpringMVCで入力チェックをするためには、Controllerの引数に
@Validや@Validatedアノテーションを付けた変数を用意する -
@Validや@Validatedアノテーションを付けた変数に、入力チェックのルールを記載する -
@Validや@Validatedアノテーションを付けた変数の次の引数に@BindingResultを記載する - 入力チェックエラーの表示は、th:errors="*フィールド名" で記載する
リダイレクトさせる
入力チェックエラーがあった場合にはリクエストされたURLではなく、元の入力画面を表示したリクエストへ遷移して、ブラウザのリロードなどで入力リクエストを多重リクエストさせたくない設計:PRGパターン1を採用することが多いかと思います。
リダイレクトするときには以下のルールに従います。
- Controllerのメソッドに
RedirectAttributesを追加する - リダイレクト用の属性を
RedirectAttributesへ格納する - 遷移先を
redirect:遷移先URLで指定する
実装例
@PostMapping("")
public ModelAndView check(ModelAndView mnv, @ModelAttribute @Validated SampleForm sampleForm, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
if (bindingResult.hasFieldErrors()) {
// MODEL_KEY_PREFIX: org.springframework.validation.BindingResult.
redirectAttributes.addFlashAttribute(BindingResult.MODEL_KEY_PREFIX + "sampleForm", bindingResult);
redirectAttributes.addFlashAttribute("sampleForm", sampleForm);
mnv.setViewName("redirect:/");
return mnv;
}
......
}
入力チェックエラーは BindingResult インスタンスに格納されています。リダイレクト先で呼び出すControllerでもエラー情報を参照できるよう、SpringMVCのデフォルト属性名である BindingResult.MODEL_KEY_PREFIX と入力項目のクラス名を合わせた値2を設定します。これはリダイレクトする際には必要です。
リダイレクトした先でエラーメッセージが表示できなくなってしまう場合がある
ところで、Controllerの実装によっては、エラーメッセージがリダイレクト先に出力されないケースがあります。それは @ModelAttribute で入力フォーム用のクラスを初期化した場合です。例えば以下です。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Controller
@AllArgsConstructor
@RequestMapping("/")
@Slf4j
public class SampleController {
@ModelAttribute
SampleForm sampleForm() {
return new SampleForm();
}
@GetMapping("")
public ModelAndView display(ModelAndView mnv, @ModelAttribute SampleForm sampleForm) {
mnv.setViewName("index");
return mnv;
}
}
リダイレクト先にて @ModelAttributeを指定したSampleFormクラスを取得すると、SpringMVCはクラスレベルで指定した @ModelAttribute SampleForm から作成し、なんとリダイレクト前に同じ名前で設定しているクラスのBindingResult の内容も初期化してしまいます。
これを防ぐためには、以下の2つの方法があります。
- リダイレクト先では
@ModelAttributeで宣言しているformクラスを指定しない - リダイレクト前に格納するエラー情報の名前をデフォルトから変更し、リダイレクト先で格納しなおす
ModelAttribute を引数に指定しない
Controllerのメソッドにて、@ModelAttributeで指定したクラスを指定しなければ、初期化されずに表示できます。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Controller
@AllArgsConstructor
@RequestMapping("/")
@Slf4j
public class SampleController {
@ModelAttribute
SampleForm sampleForm() {
return new SampleForm();
}
@GetMapping("")
public ModelAndView display(ModelAndView mnv) {
mnv.setViewName("index");
return mnv;
}
@PostMapping("")
public ModelAndView check(ModelAndView mnv, @ModelAttribute @Validated SampleForm sampleForm, BindingResult bindingResult, RedirectAttributes redirectAttributes ) {
if (bindingResult.hasFieldErrors()) {
redirectAttributes.addFlashAttribute(BindingResult.MODEL_KEY_PREFIX + "sampleForm", bindingResult);
redirectAttributes.addFlashAttribute("sampleForm", sampleForm);
mnv.setViewName("redirect:/");
return mnv;
}
mnv.addObject("message", "check ok");
mnv.setViewName("index");
return mnv;
}
}
リダイレクト前に別名で格納し、リダイレクト先で格納しなおす方法
こちらの記載方法はやや冗長になります。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Controller
@AllArgsConstructor
@RequestMapping("/")
@Slf4j
public class SampleController {
@ModelAttribute
SampleForm sampleForm() {
return new SampleForm();
}
@GetMapping("")
public ModelAndView display(ModelAndView mnv, Model model, @ModelAttribute SampleForm sampleForm) {
if (model.containsAttribute("errors")) {
mnv.addObject(BindingResult.MODEL_KEY_PREFIX + "sampleForm", model.getAttribute("errors"));
}
mnv.setViewName("index");
return mnv;
}
@PostMapping("")
public ModelAndView check(ModelAndView mnv, @ModelAttribute @Validated SampleForm sampleForm, BindingResult bindingResult, RedirectAttributes redirectAttributes ) {
if (bindingResult.hasFieldErrors()) {
redirectAttributes.addFlashAttribute("errors", bindingResult);
redirectAttributes.addFlashAttribute("sampleForm", sampleForm);
mnv.setViewName("redirect:/");
return mnv;
}
mnv.addObject("message", "check ok");
mnv.setViewName("index");
return mnv;
}
}
まとめ
SpringMVCで入力チェックエラーをリダイレクト先に表示する方法を紹介しました。
いずれかの方法で表示できますので、1つのアプリケーション内で記載方法を統一すると良いでしょう。
AIエージェントにサンプル実装を読み込ませるのも1つの手ですね。
参考実装