Spring Validation — custom constraint validator

Если вы используете Spring Validation, то вы скоро поймёте, что аннотаций @Size, @NotNull и прочих вам не хватает. Вы, разумеется, можете написать свой наследник класса org.springframework.validation.Validator, но так не хочется терять красоту и лаконичность аннотаций.

К счастью, Spring Framework позволяет расширять имеющиеся аннотации для проверки значений своими собственными, самописными. В этой статье я приведу пример создания подобной аннотации. Более того, наша аннотация будет обращаться к бину из слоя сервисов при проверке значения. Подобную технику можно использовать, например, для проверки уникальности введённых пользователем значений по какому-нибудь полю.

Предположим, что нам нужно проверить следующий класс:

Большую часть проверок мы уже сделали аннотациями, но осталось ещё кое-что. Вполне ожидаемо, что заказчик захочет сделать проверку уникальности ИНН. В данном случае существующими стандартными аннотациями из javax.validation не обойдёшься, более того, нам нужно обращаться к базе данных для проверки существования записи с введённым ИНН. В таком случае нам нужно написать свою аннотацию. Делается это так:

Обратить внимание нужно на следующее:

  • @Constraint(validatedBy = здесь указывается сам класс, который будет осуществлять проверку в соответствии с нашей новой аннотацией.
  • String message() — здесь мы указываем сообщение об ошибке по умолчанию.
  • groups() и  payload() в данном случае не пригодятся, поэтому они пустые.

В классе UniqueItnConstraintValidator осуществляется сама проверка:

На что обратить внимание:

  • Наследуемся от javax.validation.ConstraintValidator 
  • Внедряем свой бин PersonService, к которому обращаемся для проверки существования записи с таким ИНН.
  • Отключаем обработку по умолчанию с помощью disableDefaultConstraintViolation().
  • Формируем свою информацию об ошибке валидации с указанием поля “itn” и кода сообщения об ошибке в фигурных скобках "{ru.urvanov.javaexamples.customconstraintvalidator.validation.unique.itn}".
  • Возвращаем false.
  • Если бы мы просто вернули false без отключения стандартного поведения и создания своей ошибки с указанием поля, то ошибка относилась бы ко всему типу Person, а не к конкретному его полю.

Само сообщение для кода ru.urvanov.javaexamples.customconstraintvalidator.validation.unique.itn хранится в файле “src/main/resources/ValidationMessages.properties” (а локализованные версии в файлах “src/main/resources/ValidationMessages_ru_RU.properties” и аналогичных):

Созданную аннотацию указываем для всего класса Person:

Наш контекст Spring-а:

Главный класс приложения, в котором мы инициализируем контекст Spring-а и запускаем проверку для экземпляра Person:

В нашем примере мы получаем множество Set<ConstraintViolation<Person>>, но при ошибках маппинга в контроллерах Spring MVC мы будем получать результат BindingResult!

Файл “pom.xml” с зависимостями:

При запуске метода  main из “App.java” в консоль будет выведено следующее:

Ссылки:

ZIP-архив с исходными кодами проекта

Репозиторий на GitHub

Буду рад любым комментариям!

 

Spring Validation — custom constraint validator: 4 комментария

  1. Не могли бы пояснить метод isValid(….)
    Что там происходит, изначально у нашего Persona нет id и он единственный , то есть везде предается null? в итоге метод isPresent() всегда вернет false. Обычно в базе у всех Person есть id (PK). Смысл, я так понимаю проверить реальное совпадение и дублирование именно itn?

    1. Да, верно. Мы берём id персоны, найденной в базе. В базе id всегда есть. Сравниваем с id нашей Person, у нашей id вполне может быть равен null, то тогда равенство вернёт false. Если id персоны с этим ИНН в базе и нашей персоны не равны, то генерируем ошибку.
      Смысл именно в проверке дублирования ИНН.

  2. Добрый день! Могли бы Вы подсказать, есть ли способ передавать из Constraint разные сообщения(вместо default). К примеру, мы валидируем компонент и нам нужно отправлять разные сообщения для каждого поля.

    1. Думаю, что в вашем случае можно создать свою аннотацию валидации не для всего класса, а для поля. А сообщение передавать не в default, а писать для каждого поля отдельно.
      Либо надо смотреть в возможность передачи в ctx.buildConstraintViolationWithTemplate разных сообщений для каждого поля.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *