Как связать кастомный валидатор с DTO и где он вызывается в Spring
Что проверяют
Понимаете ли вы, как Bean Validation встроена в Spring, а не только умеете написать аннотацию по шаблону. Следом почти всегда спрашивают, какое исключение прилетит при ошибке и как сделать проверку, завязанную на несколько полей или на базу.
Как отвечать
Кастомное ограничение состоит из двух частей:
- аннотация: помечена
@Constraint(validatedBy = PhoneValidator.class),@Targetи@Retention(RUNTIME)и обязательно содержитmessage,groupsиpayload, иначе провайдер откажется её регистрировать; - валидатор: реализует
ConstraintValidator<Phone, String>с методомisValid. Экземпляры создаёт Spring черезLocalValidatorFactoryBean, поэтому в валидатор можно внедрить бин, например репозиторий для проверки уникальности.
В Spring Boot 3 пакеты уже jakarta.validation, а не javax.validation, и нужен spring-boot-starter-validation: веб-стартер его не подтягивает.
Где вызывается. Для @RequestBody аргумент сначала десериализует HttpMessageConverter, затем RequestResponseBodyMethodProcessor видит @Valid или @Validated и запускает валидацию. При ошибке летит MethodArgumentNotValidException, по умолчанию это ответ 400, а формат ошибок настраивают в @RestControllerAdvice. Вложенные объекты проверяются, только если на поле тоже стоит @Valid.
Второе место: сервисный слой. @Validated на классе включает проверку параметров методов через AOP-прокси, там ошибка приходит как ConstraintViolationException. Для правил на несколько полей (пароль и его подтверждение) аннотацию вешают на класс DTO с ElementType.TYPE.
Где спотыкаются
Возвращают false для null. Принято считать null валидным и комбинировать ограничение с @NotNull, иначе аннотацию нельзя использовать на необязательных полях.
Забывают @Valid на параметре или на вложенном поле, либо сам стартер валидации. Приложение запускается, аннотации молча игнорируются.
Путают исключения и вызывают @Validated-метод из того же класса. Внутренний вызов идёт мимо прокси, и проверка не срабатывает.