前文
データフィールドは一般的にビジネス要件とデータベース設計に従うので、バックエンドのパラメータ検証は必須であり、アプリケーションは入力されたデータが意味的に正しいことを保証する何らかの手段を持たなければなりません。
データ検証のポイント
データのセマンティクスが正しいことを保証するためには、検証ロジックを処理するために多くの判断が必要になります。また、レイヤー化することで検証の重複が発生し、ビジネスに関係のないコードが大量に生成されます。コードのメンテナンスには向かず、開発者の作業負担も増えます。
JSR 303 検証仕様とその実装
Hibernate Validator が提供する一般的な制約アノテーション
@Null | コメントされた要素はnullでなければなりません。 |
NotNull | コメントされた要素はNULLであってはなりません。 |
@AssertTrue | 注釈付き要素は真でなければなりません。 |
@AssertFalse | コメントされた要素はfalseでなければなりません。 |
@Min(value) | 注釈付き要素は、指定された最小値以上の数値でなければなりません。 |
@Max(value) | コメントされた要素は、その値が指定された最大値以下の数値でなければなりません。 |
@DecimalMin(value) | 注釈付き要素は、指定された最小値以上の数値でなければなりません。 |
@DecimalMax(value) | コメントされた要素は、その値が指定された最大値以下の数値でなければなりません。 |
@Size(max, min) | 注釈付き要素のサイズは、指定された範囲内でなければなりません。 |
@Digits (integer, fraction) | 注釈付き要素は、値が許容範囲内にある数値でなければなりません。 |
@Past | 注釈付き要素は過去の日付でなければなりません。 |
@Future | 注釈付き要素は未来の日付でなければなりません。 |
@Pattern(value) | コメントされた要素は、指定された正規表現にマッチしなければなりません。 |
注釈付き要素は電子メールアドレスでなければなりません。 | |
@Length | コメント文字列のサイズは、指定された範囲内でなければなりません。 |
@NotEmpty | コメントされた文字列はNULLであってはなりません。 |
@Range | 注釈付き要素は適切なスコープでなければなりません。 |
検証アノテーションの使用
xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
Validatorをカスタマイズするためのインターフェイスを実装することもできますし、制約アノテーションを使用することもできます。ファットブラザーはアノテーションが大半のニーズを満たせると感じているので、データ検証にはアノテーションを使うことを推奨します。また、アノテーションはより柔軟で、制御の粒度も細かくなります。次はデータ検証のためのアノテーションの使い方を学びましょう。
制約アノテーションの基本的な使い方
注釈制約は、以下の例に示すように、チェックが必要なメソッド・インパラメータにマークされます:
java@Data public class Student { @NotBlank(message = "氏名は必ず記入すること") private String name; @NotNull(message = "年齢は必ず記入すること") @Range(min = 1,max =50, message = "対象年齢1-50") private Integer age; @NotEmpty(message = "必要な成績") private List<Double> scores; }
POSTリクエスト
次に、POSTリクエスト用のSpring MVCインターフェースを定義します:
java@RestController @RequestMapping("/student") public class StudentController { @PostMapping("/add") public Rest<?> addStudent(@Valid @RequestBody Student student) { return RestBody.okData(student); } }
addStudent メソッドのエントリに @Valid を追加して、パラメータの検証を有効にします。MethodArgumentNotValidException;
次のようなデータを持つリクエストは例外をスローし、年齢の範囲が1-50の範囲外であることを示します。
httpPOST /student/add HTTP/1.1 Host: localhost:8888 Content-Type: application/json { "name": "felord", "age": 77, "scores": [ 55 ] }
GETリクエスト
いつものように、GETリクエスト用のインターフェースを定義します:
java@GetMapping("/get") public Rest<?> getStudent(@Valid Student student) { return RestBody.okData(student); }
MethodArgumentNotValidException;
次のリクエストは学生のスコアを正しくチェックしますが、例外の代わりにBindExceptionがスローされます。これは @RequestBody アノテーションの使用と関連しており、後の統一的な処理のためにとてもとても重要です。
httpGET /student/get?name=felord&age=12 HTTP/1.1 Host: localhost:8888
カスタム注釈
お気づきの方もいらっしゃるかもしれませんが、このマークをつけたのは上の年齢です:
java@NotNull(message = "年齢は必ず記入すること") @Range(min = 1,max =50, message = "対象年齢1-50") private Integer age;
これは、@RangeがNULLをチェックせず、範囲制約を満たしたときにのみ非NULLを扱うからです。そのため、複数のアノテーションを使用してバインドします。複数のアノテーションを繰り返し使用する必要があるシナリオがある場合は、次のアノテーションの組み合わせを使用するようにカプセル化するカスタムアノテーションを使用することができます@NotNullと@Rangeの組み合わせは、seeの使用のうち1つを模倣することができます。
javaimport org.hibernate.validator.constraints.Range; import javax.validation.Constraint; import javax.validation.Payload; import javax.validation.ReportAsSingleViolation; import javax.validation.constraints.NotNull; import javax.validation.constraintvalidation.SupportedValidationTarget; import javax.validation.constraintvalidation.ValidationTarget; import java.lang.annotation.*; /** * @author a * @since 17:31 **/ @Constraint( validatedBy = {} ) @SupportedValidationTarget({ValidationTarget.ANNOTATED_ELEMENT}) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @NotNull @Range(min = 1, max = 50) @Documented @ReportAsSingleViolation public @interface Age { // message 必須 文字列 message() default "年齢は必須で、以下の通りである。 1-50 "; // Class<?>[] groups() default {}; // Class<? extends Payload>[] payload() default {}; }
また、色の列挙が定義されている場合など、バックエンドで状態のフローに対して列挙値が定義されている場合もあり、その場合も校正が必要です:
javapublic enum Colors { RED, YELLOW, BLUE }
[
'RED',
'YELLOW',
'BLUE'
];
ConstraintValidator<A extends Annotation, T>
入力パラメータは、カスタム制約注釈のための一般的なAは、入力パラメータの型のための一般的なTは、文字列の使用は、次の実装では、色の制約を定義するためにインターフェイスの実装を必要とするColors 、の範囲を超えることができないことを願っています:
java/** * @author felord * @since 17:57 **/ public class ColorConstraintValidator implements ConstraintValidator<Color, String> { private static final Set<String> COLOR_CONSTRAINTS = new HashSet<>(); @Override public void initialize(Color constraintAnnotation) { Colors[] value = constraintAnnotation.value(); List<String> list = Arrays.stream(value) .map(Enum::name) .collect(Collectors.toList()); COLOR_CONSTRAINTS.addAll(list); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { return COLOR_CONSTRAINTS.contains(value); } }
ColorConstraintValidator;
次に、対応する制約アノテーションColorが宣言され、メタ・アノテーション@Constraintで、チェックが上記で定義された処理クラスを使用して実行されることを示す必要があります。
java/** * @author felord * @since 17:55 **/ @Constraint(validatedBy = ColorConstraintValidator.class) @Documented @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) public @interface Color { // エラーメッセージ 文字列メッセージ() default "仕様外の色"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 制約の種類 Colors[] value(); }
試しに、パラメータを制約することから始めてください:
java@Data public class Param { @Color({Colors.BLUE,Colors.YELLOW}) private String color; }
インターフェイスは上記のものと同じで、以下のインターフェイスを呼び出すとBindException例外がスローされます:
httpGET /student/color?color=CAY HTTP/1.1 Host: localhost:8888
パラメータカラーがBLUEまたはYELLOWの場合、正常に応答が得られます。