Знакомство с Jakarta Bean Validation 3.0

Знакомство с Jakarta Bean Validation 3.0, image #1

Jakarta Bean Validation — это спецификация, описывающая API для валидации объектов в рамках Jakarta EE. Вы можете использовать данную спецификацию для валидации в проектах вне зависимости от используемого фреймворка или сервера приложений. Более того, вы можете использовать Jakarta Bean Validation в полностью самостоятельных проектах, не основанных на Jakarta EE или каком-либо фреймворке.

Идейным предшественником Jakarta Bean Validation 3.0 является Bean Validation 2.0 из Java EE, однако все изменения в спецификации заключаются в переименованиях, связанных с миграцией проекта из Java EE в Jakarta EE.

В рамках этой статьи будет рассмотрен минимальный набор зависимостей, необходимый для использования Jakarta Bean Validation, описаны основные интерфейсы и ограничения или правила валидации.

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

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

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

Зависимости

Основной зависимостью является библиотека jakarta.validation:jakarta.validation-api, актуальная версия которой на момент написания статьи — 3.0.2. Добавлять данную библиотеку в зависимости проекта, как правило, не нужно — если ваш проект использует Jakarta EE API, то Bean Validation уже включён в зависимости. В остальных случаях данная библиотека будет являться транзитивной зависимостью библиотек, реализующих Bean Validation (например, org.hibernate.validator:hibernate-validator), либо библиотек зависящих от реализаций (например, org.springframework.boot:spring-boot-starter-validation).

Для примеров кода в рамках данной статьи я буду использовать Hibernate Validator 8.0.1.Final:

<dependencies>
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>8.0.1.Final</version>
    </dependency>
    <!-- Реализация Jakarta EL -->
    <dependency>
        <groupId>org.glassfish.expressly</groupId>
        <artifactId>expressly</artifactId>
        <version>5.0.0</version>
    </dependency>
</dependencies>

Основные типы Jakarta Bean Validation

Основные интерфейсы, с которыми вам предстоит взаимодействовать при использовании Jakarta Bean Validation: Validator, ExecutableValidator, ConstraintViolation и ConstraintValidator.

Validator

Интерфейс Validator используется для валидации объектов. Этот интерфейс объявляет следующие методы:

  • validate — для валидации объектов
  • validateProperty — для валидации отдельного свойства объекта
  • validateValue — для валидации значения относительно ограничений какого-либо свойства класса
  • getConstraintsForClass — для получения ограничений класса
  • unwrap — для получения экземпляра класса, специфичного для конкретной реализации Jakarta Bean Validation
  • forExecutables — для получения экземпляра ExecutableValidator, валидатора для методов

Получить валидатор можно одним из трёх способов. Получение валидатора при помощи фабрики по умолчанию:

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

Получение валидатора при помощи провайдера по умолчанию:

Validator validator = Validation.byDefaultProvider()
        .configure()
        // Дополнительные настройки
        .buildValidatorFactory()
        .getValidator();

Получение валидатора при помощи конкретного провайдера:

Validator validator = Validation.byProvider(HibernateValidator.class)
        .configure()
        // Дополнительные настройки
        .buildValidatorFactory()
        .getValidator();

ExecutableValidator

ExecutableValidator используется для валидации аргументов и возвращаемых значений методов и конструкторов. Он объявляет четыре метода:

  • validateParameters — для валидации аргументов метода
  • validateConstructorParameters — для валидации аргументов конструктора
  • validateReturnValue — для валидации значения, возвращённого методом
  • validateConstructorReturnValue — для валидации значения, возвращённого конструктором

Получить экземпляр ExecutableValidator можно при помощи метода Validator.forExecutables():

ExecutableValidator executableValidator = validator.forExecutables();

ConstraintViolation

Все методы валидации возвращают множество экземпляров ConstraintViolation. Этот интерфейс описывает ошибку валидации и объявляет следующие методы:

  • getMessage() — для получения интерполированного сообщения
  • getMessageTemplate() — для получения исходного шаблона сообщения
  • getRootBean() — для получения валидируемого объекта
  • getRootBeanClass() — для получения класса валидируемого объекта
  • getLeafBean() — для получения объекта, в котором возникла ошибка валидации
  • getExecutableParameters() — для получения аргументов валидируемого метода
  • getExecutableReturnValue() — для получения возвращаемого значения валидируемого метода
  • getPropertyPath() — для получения пути валидируемого свойства
  • getInvalidValue() — для получения значения, не прошедшего валидацию
  • getConstraintDescriptor() — для получения дескриптора ограничения, которое было нарушено
  • unwrap() — для получения более конкретной реализации ConstraintViolation, специфичной для провайдера

Пример получения отчёта об ошибках валидации:

class ValidationTest {

    Validator validator;

    @BeforeEach
    void test() {
        // 1. Создать валидируемый объект
        Candidate candidate = new Candidate(19, FEMALE);

        // 2. Провалидировать объект
        Set<ConstraintViolation<Candidate>> violations =
                this.validator(candidate);

        // 3. Произвести дополнительные проверки для violations
    }
}

ConstraintValidator

Интерфейс ConstraintValidator используется для реализации логики конкретных правил валидации и объявляет два метода:

  • initialize — для инициализации, в ходе которой можно получить доступ аннотации ограничения
  • isValid — непосредственно для реализации логики валидации.

Пример реализации ConstraintValidator для ограничения jakarta.validation.constraints.NotNull:

package org.hibernate.validator.internal.constraintvalidators.bv;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.constraints.NotNull;

public class NotNullValidator implements ConstraintValidator<NotNull, Object> {

    @Override
    public boolean isValid(Object object,
            ConstraintValidatorContext constraintValidatorContext) {
        return object != null;
    }
}

Ограничения или правила валидации

Ограничение (constraint) — это правило валидации, описываемое специфичной для Jakarta Bean Validation аннотацией. Ограничение может применяться использованием аннотаций в коде, либо при помощи XML.

Типичная аннотация ограничения имеет следующий вид:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {

    String message() default "{jakarta.validation.constraints.NotNull.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        NotNull[] value();
    }
}

Аннотация @Target задаёт список элементов, к которым аннотация ограничения может быть применена. Данный список может отличаться от продемонстрированного.

@Retention задаёт вариант доступности аннотации при компиляции исходного кода, значение RUNTIME указывает на доступность аннотации во время исполнения.

Если аннотация ограничения должна иметь возможность применения несколько раз к элементу кода, то в значении аннотации @Repeatable необходимо указать имя класса-аннотации, описывающего множественное применение. При этом описывающая множественное применение аннотация должна иметь @Target и @Retention идентичные у описываемой аннотации.

Аннотация @Documented указывает на то, что описываемая аннотация будучи применённой к элементам кода будет являться частью публичного контракта.

При помощи аннотации @Constraint можно задать список классов, валидирующих элементы кода, отмеченные описываемой аннотацией.

Аннотация ограничения должна иметь как минимум три стандартных свойства:

  • String message, содержащее шаблон сообщения, который будет использован в случае нарушения ограничения
  • Class<?>[] groups, содержащее список типов для группировки ограничений при валидации
  • Class<? extends Payload>[] payload, содержащее список типов полезной нагрузки. Данное свойство может использоваться для передачи каких-то дополнительных данных в класс-валидатор.

Кроме этих свойств класс-аннотация ограничения может содержать свойство ConstraintTarget validationAppliesTo() со значением по умолчанию ConstraintTarget.IMPLICIT, данное свойство используется для ограничений, применимых к методам и конструкторам во избежание неоднозначности объекта валидации.

Стандартные ограничения

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

@AssertFalse

Применяется к свойствам типов java.lang.Boolean и boolean, ограничивает валидируемое значение значением false.

@AssertTrue

Аннотация, аналогичная предыдущей, применяется также к свойствам типов java.lang.Boolean и boolean, но ограничивает валидируемое значение значением true.

@DecimalMax(value=, inclusive=)

Аннотация, применяемая к свойствам типов, представляющих целые числа:

  • BigDecimal
  • BigInteger
  • CharSequence
  • byte, short, int, long и их классы-обёртки

Обратите внимание на то, что float и double, а так же их классы-обёртки не поддерживаются ввиду особенностей округления.

Свойством value задаётся максимальное значение валидируемого свойства, а inclusive — включается ли максимально значение в диапазон допустимых значений.

@DecimalMin(value=, inclusive=)

Аналогичная предыдущей аннотация, также применяемая к свойствам типов, представляющих целые числа:

  • BigDecimal
  • BigInteger
  • CharSequence
  • byte, short, int, long и их классы-обёртки

double и float не поддерживаются.

Свойством value задаётся минимальное значение валидируемого свойства, а inclusive — включается ли минимальное значение в диапазон допустимых значений.

@Digits(integer=, fraction=)

Аннотация, при помощи которой можно ограничить максимальное количество знаков в целой и дробной части числа.

Как и предыдущие два ограничения поддерживает типы:

  • BigDecimal
  • BigInteger
  • CharSequence
  • byte, short, int, long и их классы-обёртки

Типы double и float не поддерживаются.

Свойство integer задаёт максимальное количество знаков целой части числа, fraction — дробной.

@Email

Аннотация, применяемая к свойствам типа CharSequence и его наследников (String), требующая, чтобы валидируемая строка была валидным адресом электронной почты.

@Future

Аннотация, ограничивающая значение свойства будущим временем.

Поддерживаемые типы валидируемых свойств:

  • java.util.Date
  • java.util.Calendar
  • java.time.Instant
  • java.time.LocalDate
  • java.time.LocalDateTime
  • java.time.LocalTime
  • java.time.MonthDay
  • java.time.OffsetDateTime
  • java.time.OffsetTime
  • java.time.Year
  • java.time.YearMonth
  • java.time.ZonedDateTime
  • java.time.chrono.HijrahDate
  • java.time.chrono.JapaneseDate
  • java.time.chrono.MinguoDate
  • java.time.chrono.ThaiBuddhistDate

@FutureOrPresent

Аннотация, ограничивающая значение свойства будущим или настоящим временем.

Список поддерживаемых типов идентичен таковому у @Future.

@Max(value=)

Аннотация, позволяющая указать максимальное значение свойства числового типа.

Поддерживаемые типы:

  • BigDecimal
  • BigInteger
  • CharSequence
  • byte, short, int, long и их классы-обёртки

double и float не поддерживаются.

@Min(value=)

Аннотация, позволяющая указать минимальное значение свойства числового типа.

Список поддерживаемых типов идентичен таковому у @Max.

@NotBlank

Аннотация, требующая, чтобы валидиуемое строковое значение не было пустой строкой или не состояло исключительно из пробельных символов.

Применяется к CharSequence и наследникам.

@NotEmpty

Аннотация, требующая, чтобы валидируемое свойство было непустым объектом.

Поддерживаемые типы:

  • CharSequence и наследники (length())
  • Collection и наследники (size())
  • Map и наследники (size())
  • Массивы (length)

@NotNull

Аннотация, требующая, чтобы валидируемое значение было отлично от null.

@Negative

Аннотация, требующая, чтобы валидируемое значение было отрицательным числом.

Поддерживаемые типы:

  • BigDecimal
  • BigInteger
  • CharSequence
  • byte, short, int, long, float, double и их классы-обёртки

Значение null считается валидным.

@NegativeOrZero

Аннотация, требующая, чтобы валидируемое значение было отрицательным числом, либо нулём.

Поддерживаемые типы:

  • BigDecimal
  • BigInteger
  • CharSequence
  • byte, short, int, long, float, double и их классы-обёртки

Значение null считается валидным.

@Null

Аннотация, требующая, чтобы валидируемое значение было null.

@Past

Аннотация, ограничивающая значение свойства прошедшим временем.

Список поддерживаемых типов идентичен таковому у @Future.

@PastOrPresent

Аннотация, ограничивающая значение свойства прошедшим или настоящим временем.

Список поддерживаемых типов идентичен таковому у @Future.

@Pattern(regex=, flags=)

Аннотация, требующая, чтобы валидируемое значение типа CharSequence соответствовало регулярному выражению.

В свойстве regex указывается регулярное выражение, а в flags — массив флагов.

@Positive

Аннотация, требующая, чтобы валидируемое значение было положительным числом.

Поддерживаемые типы:

  • BigDecimal
  • BigInteger
  • CharSequence
  • byte, short, int, long, float, double и их классы-обёртки

Значение null считается валидным.

@PositiveOrZero

Аннотация, требующая, чтобы валидируемое значение было положительным числом, либо нулём.

Поддерживаемые типы:

  • BigDecimal
  • BigInteger
  • CharSequence
  • byte, short, int, long, float, double и их классы-обёртки

Значение null считается валидным.

@Size(min=, max=)

Аннотация, ограничивающая размеры значения валидируемого свойства.

Поддерживаемые типы:

  • CharSequence и наследники (length())
  • Collection и наследники (size())
  • Map и наследники (size())
  • Массивы (length)

Свойством min задаётся минимальный размер (включительно), свойством max — максимальный (включительно).

В следующих статьях будет продемонстрировано применение ограничений для валидаций объектов и методов.

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

72 views·2 shares