Валидация объектов — Jakarta Bean Validation

В этой статье, посвящённой Jakarta Bean Validation, будет подробно рассмотрен процесс валидации объектов: применение ограничений к элементам классов, их валидация, а также валидация записей (record).

Цикл статей о Jakarta Bean Validation

Данная статья является частью цикла статей о Jakarta Bean Validation:

  • Знакомство с Jakarta Bean Validation 3.0
  • Валидация объектов (Вы сейчас её читаете)
  • Валидация методов
  • Группы валидации (В процессе написания)
  • Создание ограничения и валидатора (В процессе написания)
  • Интерполяция сообщений (В процессе написания)
  • Конфигурация валидатора (В процессе написания)

Ограничения

В рамках классов ограничения могут применяться к полям, свойствам и самим классам. Кроме этого ограничения суммируются при наследовании.

Ограничение поля

При работе с объектами возможна валидация полей и свойств классов, конкретных значений и объектов целиком. Примеры применения ограничений ниже будут продемонстрированы ниже при помощи аннотаций и XML, использовать одновременно и аннотации, и XML не нужно, старайтесь выбрать какой-то один подход, наиболее удобный для вас.

При использовании ограничения поля аннотация ограничения указывается непосредственно у объявленного поля класса:

При помощи аннотации @Min задано ограничение, согласно которому значение поля age не должно быть меньше 18, про данное ограничение более подробно будет написано ниже в этой статье.

В XML ограничения полей задаются в блоке <field>:

Ограничение свойства

Если класс соответствует спецификации Java Beans, то ограничение можно применить к методу get..:

Применение аннотаций ограничений к get…​-методам может вызывать некоторую путаницу, так как применение аннотаций ограничений к возвращаемым значениям методов и аннотаций перекрёстных ограничений к аргументам методов выглядит схожим образом, что будет показано ниже.

В XML ограничение свойства задаётся в блоке <getter> и не вызывает такой путаницы:

Ограничение класса

Jakarta Bean Validation предоставляет возможность использования ограничений для классов. Это может быть полезным, когда используется какая-то сложная логика валидации, зависящая от значений свойств объекта.

Каких-то стандартных ограничений для классов спецификация не предоставляет.

Ограничение класса задаётся аннотацией для валидируемого класса:

То же самое может быть реализовано в XML при помощи <class>:

@ValidCandidate — это собственное ограничение. Создание и использование собственных ограничений будет рассмотрено в отдельной статье.

Ограничение элементов контейнера

Jakarta Bean Validation позволяет задавать ограничения для объектов, находящихся в контейнерах. Поддерживаются следующие виды контейнеров:

  • Наследники и реализации java.util.Iterable, включая ListSetQueue и т.д.
  • Наследники и реализации java.util.Map
  • java.util.Optional и родственные классы
  • И некоторые другие

Для использования ограничения к элементам контейнеров аннотацию нужно добавить к типу обобщения (дженерика) контейнера:

Ограничение @NotNull указывает на то, что объект не должен быть null.

Применение аннотации ограничения к типу обобщения возможно благодаря значению ElementType.TYPE_USE в @Target.

Ограничения элементов контейнеров могут быть установлены в XML при помощи <container-element-type> следующим образом:

Валидируемый контейнер может быть любым элементом, который можно провалидировать: свойством класса, аргументом метода или конструктора или возвращаемым значением метода.

Наследование ограничений

Применяемые к элементам классов и интерфейсов ограничения сохраняются при использовании наследования и распространяются на дочерние типы. Наследники в свою очередь могут добавлять собственные ограничения.

Допустим, в проекте есть интерфейс OrderDomainEvent:

а так же класс AbstractOrderDomainEvent:

и класс OrderCreated:

Класс OrderCreated в дополнение к собственным ограничениям унаследует ограничения, объявленные в OrderDomainEvent и AbstractOrderDomainEvent.

Особенности записей (record)

В JDK 16 появился новый тип классов — запись (record), который может применяться для описания объектов передачи данных (Data transfer object; DTO).

Ограничения можно применять к элементам записей, ровно, как и валидировать их.

Пример применения ограничений к записи:

Особенности применения ограничений к элементам записи при помощи аннотаций:

  • Ограничение, указанное для класса (@ValidCandidate), применяется не только к классу, но и к конструктору
  • Ограничения свойств применяются так же к аргументам главного конструктора и методам, возвращающим значения свойств

В XML можно применять ограничения индивидуально к элементам записей.

Идентичный набор ограничений для обычного класса выглядел бы следующим образом:

Валидация

При работе с классами возможна валидация объектов целиком, отдельных их свойств, а также значений на основе правил валидации свойств классов.

Валидация объектов

Валидация объектов выполняется при помощи метода Validator.validate(), который принимает два аргумента:

  • Валидируемый объект
  • Список групп валидации, который может быть пустым (здесь и далее будет применяться jakarta.validation.groups.Default, хоть явное указание этой группы и не требуется)

Результатом валидации является множество экземпляров ConstraintViolation, описывающих ошибки валидации. В случае успеха метод validate должен вернуть пустое множество.

Валидация свойств объекта

Jakarta Bean Validation предоставляет возможность валидации конкретных свойств объектов, для этого может быть использован метод validateProperty, который принимает следующие аргументы:

  • Валидируемый объект
  • Название валидируемого свойства
  • Список групп валидации

Данный метод, так же как и validate, возвращает множество ошибок валидации в случае несоответствия значений правилам валидации.

Валидация значений

Так же есть метод validateValue, позволяющий валидировать значения. При его помощи можно провести валидацию значения до присвоения свойству, т.е. провести превалидацию. При этом правила валидации, объявленные для указанного свойства валидируемого класса, будут применены к валидируемому значению. Данный метод принимает четыре аргумента:

  • Класс валидируемого объекта
  • Название валидируемого свойства
  • Валидируемое значение
  • Список групп валидации

Каскадирование валидации

По умолчанию валидация применяется только к валидируемому объекту, в то время как валидация вложенных объектов не производится. Если при валидации объекта требуется провалидировать и вложенные объекты, то такие объекты должны быть отмечены аннотацией @Valid:

То же самое может быть достигнуто при помощи <valid> в XML:

При валидации экземпляра CandidatesGroup будет проведена проверка, что свойство candidates не является null, а также, что все элементы этого списка — тоже валидные объекты. Если хотя бы один элемент списка candidates нарушает ограничения, то валидация завершится ошибкой.

Полезные ссылки