Spring Boot エレガントなパラメータの検証
最近、社内の他事業グループのプロジェクトでリクエストパラメータのチェックにif elseを使用しているところが多く、パラメータを受け取るところはほぼどこでもパラメータチェックが多いことが分かりました。
この記事では、 Spring Boot プロジェクトで
validation
リクエスト パラメーターのエレガントな検証を使用する方法を紹介します。
すべての準備作業が完了し、パラメータを検証する必要がある新しいインターフェイスができたと仮定します。
まず、次のように、インターフェイス パラメーターの前に注釈を追加して@Valid
、検証の必要性を示します。controller
@PostMapping
public HttpResponse demo(@Valid @RequestBody DemoDTO dto) {
return dto.getName();
}
別のvalidation
メモを追加します。
@NotNull(message = "请输入姓名")
private String name;
さて、呼び出しの出力は次のとおりです。
{ "code": 500, "msg": "请输入姓名" }
とても簡単ですね? 検証の必要性を示すアノテーションと、検証方法を示すアノテーションの 2 つのアノテーションを追加するだけで、すべて完了します。
しかし、その前に、他の作業を行う必要があります。
- 統一された応答応答クラスを定義する
- インターセプト例外
まず、応答をカプセル化するクラスを定義しますHttpResponse
。
@Setter
@Getter
@Builder
public class HttpResponse {
private Integer code;
private String msg;
}
次に、例外をグローバルにインターセプトして、このオブジェクトを返します。完全な例外処理については後で説明します。ここでは、その構成方法のみを示します。
@ControllerAdvice
public class BaseExceptionHandler {
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public HttpResponse handle(MethodArgumentNotValidException e) {
FieldError fieldError = e.getBindingResult().getFieldError();
return HttpResponse.builder().code(500).msg(fieldError.getDefaultMessage()).build();
}
}
ここでMethodArgumentNotValidException
挙げた例外は検証時にスローされます。実際には検証失敗により他の例外もスローされますが、これらは一般に開発プロセスの問題によって引き起こされるものであり、ユーザーの入力とは関係がないため、「システム例外」に応答できます。 」に統一して対応する必要はありません。
例外を処理する他の方法もありますが、統合インターセプトが最も簡単です。開発プロセス中に、検証失敗の処理方法を考慮する必要はありません。ルールを設定するには、いくつかのアノテーションを記述するだけで済みます。
Bean 検証の概要
Bean Validation はデータ検証に関する一連の仕様です。JSR 303 – Bean Validation の仕様jcp.org/en/jsr/deta…
Bean Validation は、一連のメタデータ モデルと API を定義します。Hibernate Validator は Bean Validation のリファレンス実装です。JSR 303 仕様の組み込み制約に加えて、一般的に使用される追加の制約実装も定義されています。www.hibernate.org/subprojects …
Bean Validation の制約
制約 | 説明する |
---|---|
@Null |
注釈が付けられた要素は次のとおりである必要がありますnull |
@NotNull |
注釈付きの要素は次のとおりであってはなりませんnull |
@AssertTrue |
注釈が付けられた要素は次のとおりである必要がありますtrue |
@AssertFalse |
注釈が付けられた要素は次のとおりである必要がありますfalse |
@Min(value) |
注釈付きの要素は数値である必要があります。 |
その値は、指定された最小値以上である必要があります | |
@Max(value) |
注釈付きの要素は数値である必要があります。 |
その値は、指定された最大値以下でなければなりません | |
@DecimalMin(value) |
注釈付きの要素は数値である必要があります。 |
その値は、指定された最小値以上である必要があります | |
@DecimalMax(value) |
注釈付きの要素は数値である必要があります。 |
その値は、指定された最大値以下でなければなりません | |
@Size(max, min) |
注釈付き要素のサイズは指定された範囲内である必要があります |
@Digits (integer, fraction) |
注釈付きの要素は数値である必要があります。 |
その値は許容範囲内である必要があります | |
@Past |
注釈が付けられた要素は過去の日付である必要があります |
@Future |
注釈付きの要素は将来の日付である必要があります |
@Pattern(value) |
注釈が付けられた要素は、指定された正規表現と一致する必要があります |
Hibernate Validator の追加制約
制約 | 説明する |
---|---|
@Email |
注釈付きの要素は電子メール アドレスである必要があります |
@Length |
注釈付き文字列のサイズは指定された範囲内である必要があります |
@NotEmpty |
コメント文字列は空であってはなりません |
@Range |
注釈が付けられた要素は適切なスコープ内に存在する必要があります |
制約は通常、アノテーションとそれに対応する制約バリデータで構成され、これらは 1 対多の関係になります。つまり、1 つのアノテーションに対応する複数の制約バリデーターが存在する可能性があります。実行時に、Bean Validation フレームワーク自体が適切な制約バリデーターを選択し、アノテーションが付けられた要素のタイプに基づいてデータを検証します。
ユーザーのアプリケーションでは、より複雑な制約が必要になる場合があります。Bean Validation は、制約を拡張するためのメカニズムを提供します。これは 2 つの方法で実現できます。1 つは既存の制約を組み合わせてより複雑な制約を生成する方法、もう 1 つは完全に新しい制約を開発する方法です。カスタムバリデーターを追加する方法については後ほど説明します。
制約条件の適用範囲
-
@Null
-
注: 注釈が付けられた要素は次のとおりである必要があります。
null
-
適用範囲:
Object
-
-
@NotNull
-
注: 注釈付きの要素は次のとおりであってはなりません
null
-
適用範囲:
Object
-
-
@AssertTrue
-
注: 注釈が付けられた要素は次のとおりである必要があります。
true
-
適用範囲:
boolean
、Boolean
-
-
@AssertFalse
-
注: 注釈が付けられた要素は次のとおりである必要があります。
false
-
適用範囲:
boolean
、Boolean
-
-
@Min(value)
-
説明: 注釈付きの要素は数値である必要があり、その値は指定された最小値以上である必要があります。
-
適用範囲:
BigDecimal
、BigInteger
、byte
、Byte
、short
、Short
、int
、Integer
、long
Long
-
-
@Max(value)
-
説明: 注釈付きの要素は数値である必要があり、その値は指定された最大値以下である必要があります。
-
適用範囲:
BigDecimal
、BigInteger
、byte
、Byte
、short
、Short
、int
、Integer
、long
Long
-
-
@DecimalMin(value)
-
説明: 注釈付きの要素は数値である必要があり、その値は指定された最小値以上である必要があります。
-
適用範囲:
BigDecimal
、BigInteger
、CharSequence
、byte
、Byte
、short
、Short
、int
、Integer
、long
Long
-
-
@DecimalMax(value)
-
説明: 注釈付きの要素は数値である必要があり、その値は指定された最大値以下である必要があります。
-
適用範囲:
BigDecimal
、BigInteger
、CharSequence
、byte
、Byte
、short
、Short
、int
、Integer
、long
Long
-
-
@Size(max, min)
-
説明: 注釈付き要素のサイズは、指定された範囲内である必要があります
-
適用範囲:
CharSequence
、Collection
、Map
、Array
-
-
@Digits (integer, fraction)
-
注: 注釈付きの要素は数値である必要があり、その値は許容範囲内である必要があります。
-
適用範囲:
BigDecimal
、BigInteger
、CharSequence
、byte Byte
、short Short
、int Integer
、long Long
-
-
@Past
-
注: 注釈が付けられた要素は過去の日付である必要があります
-
適用範囲:
Date
、Calendar
、Instant
、LocalDate
、LocalDateTime
、LocalTime
、MonthDay
、OffsetDateTime
、OffsetTime
、Year
、YearMonth
、ZonedDateTime
、HijrahDate
、JapaneseDate
MinguoDate
ThaiBuddhistDate
-
-
@Future
-
注: 注釈付きの要素は将来の日付である必要があります
-
適用範囲:
Date
、Calendar
、Instant
、LocalDate
、LocalDateTime
、LocalTime
、MonthDay
、OffsetDateTime
、OffsetTime
、Year
、YearMonth
、ZonedDateTime
、HijrahDate
、JapaneseDate
MinguoDate
ThaiBuddhistDate
-
-
@Pattern(value)
-
説明: 注釈付き要素は、指定された正規表現に準拠する必要があります。
- 適用範囲:
CharSequence
、null
- 適用範囲:
-
@Email
- 注: 注釈付きの要素は電子メール アドレスである必要があります
- 適用範囲:
CharSequence
-
@Length
- 説明: 注釈付き文字列のサイズは、指定された範囲内である必要があります
- 適用範囲:
-
@NotEmpty
- 注: コメントされた文字列は空であってはなりません
- 適用範囲:
-
@Range
- 注: 注釈付きの要素は適切な範囲内にある必要があります
- 適用範囲:
コード認証を使用する
ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); Set<ConstraintViolation<DemoDTO>> violations = validator.validate(dto);
実際、Spring Boot はすでに定義されており、直接信頼できます。
@Resource
private Validator validator;
カスタム制約
通常は組み込みの制約で十分ですが、特殊な状況が発生する場合は、制約を自分で定義する必要があります。
他の制約を組み合わせる
元の制約を独自の注釈に直接追加します。年齢属性を制約する新しい注釈を追加します。年齢を 0 ~ 150 の間に制限する場合:
@Max(150)
@Min(0)
@Target({
METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {
})
public @interface Age {
@OverridesAttribute(constraint = Max.class, name = "message") @OverridesAttribute(constraint = Min.class, name = "message") String message() default "年龄超出范围";
Class<?>[] groups() default {
};
Class<? extends Payload>[] payload() default {
};
}
@OverridesAttribute
アノテーションは、結合された制約の一部の属性をオーバーライドできます。ここではメッセージのみを説明します。他の属性もオーバーライドできます。
簡単にテストしてみましょう。
@Age(message = "这年龄不太对吧")
private Integer age;
出力:
{ "code": 500, "msg": "这年龄不太对吧" }
新しい制約
これはまだ年齢を制限する例ですが、今回は制約アノテーションから始めてロジックを自分たちで書きます。
@Target({
METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = AgeValidtor.class)
public @interface Age {
String message() default "年龄超出范围";
Class<?>[] groups() default {
};
Class<? extends Payload>[] payload() default {
};
}
ここで注意してください
@Constraint(validatedBy = AgeValidtor.class)
、それは私たち自身で実装したバリデータを設定します
次にバリデーターを実装します。
public class AgeValidtor implements ConstraintValidator<Age, Integer> {
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return value == null || (value >= 0 && value <= 150);
}
}
バリデーターによって実装されるインターフェースには 2 つの汎用タイプがあります。前者は
Age
制約アノテーションを表し、後者はInteger
このタイプの属性を検証するために使用されるバリデーターを表します。前述のように、制約アノテーションは、制約アノテーションに応じて複数のバリデーターに関連付けることができます。検証する属性のタイプ。適切なバリデーターを選択します。
出力は依然として同じです。
{ "code": 500, "msg": "这年龄不太对吧" }
ネストされた検証
オブジェクト内に検証が必要な別のオブジェクトがある場合は、このオブジェクトのプロパティの上に@Valid
注釈を追加する必要があります。
@Getter
@Setter
@ToString
public class DemoDTO {
@Valid
private InlineObject inlineObject;
}
通常のパラメータの確認方法
インターフェイスの検証に加えて、通常のメソッドも検証できます。まず、検証する必要があるメソッドが配置されているクラスにアノテーションを追加します@Validated
。
@Validated
@Service
public class DemoService {
public void demo(@Valid DemoDTO dto) {
}
}
通常の呼び出し後に戻ります。
{ "code": 500, "msg": "这年龄不太对吧" }
知らせ:
-
aopの仕組みに基づき、検証済みのメソッドをコンポーネントとして登録する必要がある
-
注釈はクラスにのみ追加でき
@Validated
、個々のメソッドには追加できません。 -
スローされる例外は であり
ConstraintViolationException
、これは個別にインターセプトする必要があります。例:
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
public HttpResponse handle(ConstraintViolationException e) {
return HttpResponse.builder().code(500).msg(e.getConstraintViolations().stream().findFirst().map(ConstraintViolation::getMessage).orElse("参数校验失败")).build();
}
グループ認証
多くの場合、クラスは追加と更新に使用されますが、更新時に属性をid
空にすることはできません。また、追加時に属性を空にする必要があります。追加時に属性が何であるかは関係ありません。
このとき、状況に応じて異なる検証ルールを使用する必要があります。まず、新規と更新の 2 つの状況グループを定義します。
public interface ValidationGroup {
interface Create extends Default {
}
interface Update extends Default {
}
}
知らせ:
- インターフェースとしてのみ定義可能
- 継承が必要です
javax.validation.groups.Default
。そうでない場合、groups
追加されていない他の制約は他のグループとみなされます。
次に、次の 2 つのケースで検証する必要がある属性を定義します。
@NotNull(message = "更新时id必填", groups = ValidationGroup.Update.class)
private Integer id;
@NotNull(message = "新增时name必填", groups = ValidationGroup.Create.class)
private String name;
@NotNull(message = "任何情况age必填")
private Integer age;
次の 2 つの状況に応じてインターフェイスを定義します。
@PostMapping("/create")
public String create(@Validated(ValidationGroup.Create.class) @RequestBody DemoDTO dto) {
return dto.getName();
}
@PostMapping("/update")
public String update(@Validated(ValidationGroup.Update.class) @RequestBody DemoDTO dto) {
return dto.getName();
}
このとき、2 つのインターフェイスをそれぞれ呼び出すために固定入力パラメーターを使用します。パラメーターは次のとおりです。
{ "age": 12 }
2 つの場合の出力:
{ "code": 500, "msg": "新增时name必填" }
{ "code": 500, "msg": "更新时id必填" }