カスタム制約アノテーション
ConstraintValidatorの実装を使って、カスタムの@UniqueEmailアノテーションを作成します。
「カスタム制約アノテーション」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
標準の制約だけでは不十分な場合
標準の Bean Validation アノテーションは一般的なケースに対応します。ドメイン固有のルール(例: メールアドレスの一意性、有効な IBAN、パスワードの強度)には、カスタム制約アノテーションを作成します。
制約アノテーションの定義
@Constraint(validatedBy = ...) を付けたアノテーションを作成し、必須の message、groups、payload 要素と、適切な保持ポリシーおよび対象を指定します。
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@Documented
@Constraint(validatedBy = UniqueEmailValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface UniqueEmail {
String message() default "Email already registered";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}ConstraintValidator の実装
ConstraintValidator<UniqueEmail, String> を実装します。initialize メソッドでアノテーションの要素を読み取り、isValid で実際の検証を行います。
@Component
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
@Autowired private UserRepository repo;
@Override
public boolean isValid(String email, ConstraintValidatorContext ctx) {
if (email == null) return true; // let @NotBlank handle null
return !repo.existsByEmail(email);
}
}カスタム制約の使用
フィールド、パラメーター、または戻り値に、標準の制約と同じようにアノテーションを適用します。
public record CreateUserRequest(
@NotBlank
String name,
@NotBlank @Email @UniqueEmail
String email
) {}補間によるカスタムメッセージ
{elementName} を使ってメッセージ内でアノテーションの要素を参照します。ctx.buildConstraintViolationWithTemplate() を使うと、動的なデータを含むカスタムメッセージを追加できます。
@Constraint(validatedBy = RangeValidator.class)
public @interface InRange {
int min() default 0;
int max() default 100;
String message() default "Must be between {min} and {max}";
// ...
}クラスレベルの制約
複数のフィールドをまとめて検証するには、クラスレベルで制約を適用します。パスワード確認のようなフィールド間のルールに役立ちます。
@Documented
@Constraint(validatedBy = PasswordMatchValidator.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PasswordMatch {
String message() default "Passwords do not match";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}クラスレベルのバリデーター実装
バリデーターはオブジェクト全体を受け取ります。複数のフィールドにアクセスして、フィールド間の検証を行います。
public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, ChangePasswordRequest> {
public boolean isValid(ChangePasswordRequest req, ConstraintValidatorContext ctx) {
if (req.getNewPassword() == null) return true;
boolean match = req.getNewPassword().equals(req.getConfirmPassword());
if (!match) {
ctx.disableDefaultConstraintViolation();
ctx.buildConstraintViolationWithTemplate("Passwords do not match")
.addPropertyNode("confirmPassword").addConstraintViolation();
}
return match;
}
}制約の合成
カスタムアノテーションに既存の制約を付けて、メタ制約を作成します。両方の制約が自動的に適用されます。
@NotBlank
@Email
@Size(max = 255)
@Constraint(validatedBy = {})
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidEmail {
String message() default "Invalid email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}複数の違反の返却
ctx.disableDefaultConstraintViolation() を使用し、1 回のバリデーター実行で見つかった各問題に対して個別の違反メッセージを追加します。
public boolean isValid(String password, ConstraintValidatorContext ctx) {
List<String> issues = new ArrayList<>();
if (password.length() < 8) issues.add("Too short");
if (!password.matches(".*\\d.*")) issues.add("Must contain a digit");
if (issues.isEmpty()) return true;
ctx.disableDefaultConstraintViolation();
issues.forEach(msg -> ctx.buildConstraintViolationWithTemplate(msg).addConstraintViolation());
return false;
}Spring 管理のバリデーター
UniqueEmailValidator に @Component が付いているため、Spring はリポジトリを注入します。これはバリデーターが Spring のバリデーターを通じて初期化される場合にのみ機能します。Spring Boot はこれを自動的に構成します。
カスタムバリデーターのテスト
純粋な検証ロジックのバリデーターは、Spring コンテキストを使わずに直接ユニットテストできます。統合テストには Validation.buildDefaultValidatorFactory() を使用します。
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
CreateUserRequest req = new CreateUserRequest("", "bad-email", null);
Set<ConstraintViolation<CreateUserRequest>> violations = validator.validate(req);
System.out.println(violations.size()); // 3確認問題
カスタム制約バリデーターはどのインターフェースを実装しますか?
まとめ
@Constraint(validatedBy=...) を付けた @interface を作成します。ConstraintValidator を実装します。Spring の依存性注入には @Component を使用します。クラスレベルの制約では、複数のフィールドをまとめて検証できます。既存の制約を合成して重複を避けます。
よくある質問
「カスタム制約アノテーション」レッスンは無料ですか?
はい。「カスタム制約アノテーション」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「カスタム制約アノテーション」で何を学びますか?
ConstraintValidatorの実装を使って、カスタムの@UniqueEmailアノテーションを作成します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「カスタム制約アノテーション」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Bean Validation:@NotNull、@Size、@Pattern
- カスタム制約アノテーション
- @ControllerAdviceによるグローバル例外処理
- RFC 7807 Problem Detailsと一貫したエラーレスポンス