0Pricing
Java Academy · Урок

Пользовательские аннотации ограничений

Создайте пользовательскую аннотацию @UniqueEmail с реализацией ConstraintValidator.

«Пользовательские аннотации ограничений» — бесплатный урок Java Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Когда стандартных ограничений недостаточно

Стандартные аннотации проверки компонентов охватывают распространённые случаи. Для правил, специфичных для предметной области (например, уникального адреса электронной почты, допустимого 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() и добавляйте отдельное сообщение о нарушении для каждой обнаруженной проблемы за один проход валидатора.

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

Быстрая проверка

Какой интерфейс реализует пользовательский валидатор ограничений?

Итоги

Создайте @interface с помощью @Constraint(validatedBy=...). Реализуйте ConstraintValidator. Используйте @Component для внедрения зависимостей Spring. Ограничения на уровне класса проверяют несколько полей вместе. Объединяйте существующие ограничения, чтобы не дублировать код.

Часто задаваемые вопросы

Урок «Пользовательские аннотации ограничений» бесплатный?

Да — полный текст урока «Пользовательские аннотации ограничений» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «Пользовательские аннотации ограничений»?

Создайте пользовательскую аннотацию @UniqueEmail с реализацией ConstraintValidator. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Пользовательские аннотации ограничений»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Проверка компонентов: @NotNull, @Size, @Pattern
  2. Пользовательские аннотации ограничений
  3. Глобальная обработка исключений с @ControllerAdvice
  4. Детали проблемы RFC 7807 и единообразные ответы с ошибками
← Назад к Java Academy