Spring の Bean Validation でエラーメッセージにフィールド名を埋め込む

Bean Validation と書いたが、メッセージにフィールド名を埋め込むのは、正確には Spring が提供する機能らしい。

サンプルコード


アノテーションは以下の通り。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy = { SampleValidator.class })
public @interface SampleValidation {

    String value();

    String message() default "{com.example.demo.constraint.SampleValidation.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

今回は Controller で @Validated を利用してフォームバリデーションをするパターン。

@RestController
public class DemoController {

    @Autowired
    MessageSource messageSource;

    @PostMapping("index")
    public String index(@RequestBody @Validated FooForm form, Errors errors) {
        if (errors.hasErrors()) {
            errors.getAllErrors().stream()
                    .map(e -> messageSource.getMessage(e, Locale.getDefault()))
                    .forEach(System.out::println);
            return "NG";
        }
        return "OK";
    }

    static class FooForm {
        @SampleValidation("foo")
        private String name;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }

}

プロパティが未定義の場合、フォームクラスのフィールド名がメッセージの {0} にそのまま出力される。

com.example.demo.constraint.SampleValidation.message={0} is invalid.
name is invalid.

フィールド名をキーにしてプロパティを定義した場合、定義したフィールド名がメッセージの {0} に埋め込まれて出力される。

com.example.demo.constraint.SampleValidation.message={0} is invalid.
name=Name
Name is invalid.

<フィールド名> 形式と <フォームクラス>.<フィールド名> 形式を一緒に定義した場合、<フォームクラス.フィールド名> 形式のプロパティが優先される。

com.example.demo.constraint.SampleValidation.message={0} is invalid.
name=Name
fooForm.name=NAME
NAME is invalid.