Springのcontrollerにおけるvalidation

Spring Frameworkにおけるvalidation

http://terasolunaorg.github.io/guideline/5.4.1.RELEASE/ja/ArchitectureInDetail/WebApplicationDetail/Validation.html

詳しくは上記リンクも参照したほうが良いですが、 カジュアルに使うように残します。

Hibernate Validator

実装はHibernate Validatorなので、javax.validation.constraintsアノテーションを使います。

主なアノテーション

@NotNull // nullでないことをチェック

/**
* Stringに対して使えるアノテーション
*/
@NotEmpty // 空文字列でないことをチェック
@Size(min = 1, max = 10) // 文字列の長さが1以上10以下であることをチェック
@Pattern(regexp = "[0-9A-Z]*") // "[0-9A-Z]*"の正規表現にマッチしているかチェック
@Email // Emailアドレスとしてvalidなものかチェック

/**
* Integerなど整数型に対して使えるアノテーション
*/
@Min(3) // 3以上であることをチェック
@Max(100) // 以下であることをチェック

Formに対するvalidation

制約を記したFormクラスを用意して、コントローラで受け取るのが基本です。

Formクラス

ビューの入力フォームに対応するFormクラスを書きます。

@Data
public final class SearchForm {
    @NotNull
    @Size(min = 1, max = 100)
    @Pattern(regexp = "[0-9a-zA-z]*")
    private String message;
}

Controllerクラス

Formのバリデーションに失敗するとBindingResultにエラーが入ります。

@Controller
@RequestMapping("")
public class SearchController {
    @PostMapping("serach")
    public ModelAndView search(
        @Validated @ModelAttribute final SearchForm searchForm,
        final BindingResult bindingResult
    ) {
        final ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("hoge");
        // Formのバリデーションに失敗するとBindingResultにエラーが入る
        if (bindingResult.hasErrors()) {
            // エラー時の処理
        }

        // ...

        return modelAndView;
    }
}

エラー時の実際の処理は、エラーページに飛ばしたり、 modelに何かを付け加えたりといったものになります。

エラーメッセージは、デフォルトで親切なものが出ます。
Constraintごとにmessage属性を付与して、カスタムすることができますが、
セキュリティ上の懸念がある場合は、modelに付与して、一括で同じメッセージを出しても良いと思います。

Form以外に対するvalidation

Form以外の普通の引数に対するvalidationでも、不正なリクエストを弾く場合などで、Hibernate Validatorは有用です。

@Controller
@RequestMapping("")
public class HogeController {
    @PostMapping("add")
    public ModelAndView add(
        @RequestParam("quantity")
        @NotNull
        @Min(1)
        @Max(100)
        @Valid final Integer quantity) {

        // ...

    }
}

(ここの場合Springの@Validatedではなく、Hibernateの方の@ValidでもOKです)

ここでinvalidだった場合、中に入る前に、javax.validation.ConstraintViolationExceptionが投げられます。

Springの場合はControllerで投げられた例外は@ExceptionHandlerで拾うことができるので、
@ControllerAdviceの中に書いておけば、全ての@Controllerから投げられたConstraintViolationExceptionを拾うことができます。

@ControllerAdvice
public class MyExceptionHandler {
    // 400 Bad Requestを返してエラーページに飛ばす
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleConstraintViolationException() {
        // ログ落としたり...
        
        return "error";
    }
}

書いてないこと

自前のconstraint annotationを作ることもできます。
長くなったので、また今度ということで… (忘れる😩)