0Pricing
Java Academy · レッスン

カスタム制約アノテーション

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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Bean Validation:@NotNull、@Size、@Pattern
  2. カスタム制約アノテーション
  3. @ControllerAdviceによるグローバル例外処理
  4. RFC 7807 Problem Detailsと一貫したエラーレスポンス
← Java Academyに戻る