エキサイト株式会社メディア開発の佐々木です。
Javaには、JSR303 Bean Validationという私の好きなValidation仕様があります。@Valid
をつければ、クラスのフィールドに@Empty
や@Min(1)
のようなアノテーションをつけるだけで、値のバリデーションが可能です。Springにはこれを拡張した@Validated
があります。この使い方について軽く触れます。
@Validはなに?
これはJavaの標準仕様で、クラスのフィールドにアノテーションをつけて、所定メソッドを実行するとバリデーションを行ってくれます。
@Data class Form { @NotEmpty private String name; } Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); // validatorのインスタンス生成 Form form = new Form(); // データクラスのインスタンス Set<ConstraintViolation<Form>> validate = validator.validate(form); // バリデーションの実行
フィールドに付けられたアノテーションの情報を元にバリデーションをしてくれます。フィールドの仕様とバリデーションがセットになっているので視認性もよく使い勝手はいいです。ただ、使い勝手が良すぎるせいで、ある程度約束を守ってもらわないと設定がバラバラになってしまうという問題もあります。
@Validatedはなに?
カンタンにいうと、@Valid
を拡張したものが、SpringFramework内にあります。拡張の主なポイントはグループの指定です。
グループの指定とは?
とあるユースケースで、バリデーションを指定したいフィールドを絞りたいことがあるかと思います。1つのデータクラスにリクエストパラメータを入れるのですが、入力フォームが多段になっていたり、簡易フォームと詳細フォームでわかれていたりとあると思います。そういうユースケースに効果を発揮します。
実装
下記のような実装があります。simple
のエンドポイントは、 id
とname
は必須、detail
の方のエンドポイントは、simple
のエンドポイントに加えて、age
とnickName
が必須になります。groupsという属性を定義することによって、バリデーションをどこまで行うかの識別を行っています。
@RestController @RequestMapping("valid") public class ValidController { @GetMapping("simple") public Form simple(@Validated(value = {SimpleForm.class}) Form form) { return form; } @GetMapping("detail") public Form detail(@Validated(value = {SimpleForm.class, DetailForm.class}) Form form) { return form; } @Data static class Form { @NotNull(groups = {SimpleForm.class}) private Integer id; @NotBlank(groups = {SimpleForm.class}) private String name; @NotNull(groups = {DetailForm.class}) private Integer age; @NotBlank(groups = {DetailForm.class}) private String nickName; } }
simpleのエンドポイント
@Validated(value = {SimpleForm.class}) Form form
が引数に定義されることによって、Form
クラスのフィールドにgroups = {SimpleForm.class}
のアノテーションが付与されているところだけバリデーションが実行されます。
$ curl "http://localhost:8080/valid/simple?id=1&name=sample" {"id":1,"name":"sample","age":null,"nickName":null}
detailのエンドポイント
simpleのエンドポイントをdetailに変えて、クエリパラメータはそのままに実行してみます。
$ curl "http://localhost:8080/valid/detail?id=1&name=sample" {"timestamp":"2021-04-29T15:17:56.903+00:00","status":400,"error":"Bad Request","trace":"org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 2 errors\nField error in object 'form' on field 'age': rejected value [null]; codes [NotNull.form.age,NotNull.age,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [form.age,age]; arguments []; default message [age]]; default message [null は許可されていません] ....
上記のようにエラーがバリデーションエラーが発生しています。これは先程のsimple
のエンドポイントとは異なるバリデーション設定がされています。@Validated(value = {SimpleForm.class, DetailForm.class}) Form form
のバリデーション設定で、DetailForm.class
が追加されています。こちらが追加されている為、 Formクラスに定義してあるとおり、age
とnickName
の定義が必須になっています。
さいごに
@Validated
についてカンタンに説明させていただきました。気をつけねばならないのが、 groups
をつけると@Valid
は動作しなくなるというのがあります。ただ、@Validated
はSpring依存になってしまうので、早いところ共通仕様をだしてほしいところであります