Проверка элементов контейнера с помощью Bean Validation 2.0

Проверка контейнерных элементов с помощью Bean Validation 2.0

1. обзор

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

Эта новая функциональность использует преимущества аннотаций типов, представленных в Java 8. Поэтому для работы требуется версия Java 8 или выше.

Аннотации проверки могут быть добавлены к контейнерам, таким как коллекции, объектыOptional и другие встроенные, а также настраиваемые контейнеры.

Чтобы получить представление оJava Bean Validation и о том, как настроить нужные нам зависимостиMaven, ознакомьтесь с нашимprevious article here.

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

2. Элементы коллекции

Мы можем добавлять аннотации проверки к элементам коллекций типаjava.util.Iterable,java.util.List иjava.util.Map.

Давайте посмотрим на пример проверки элементов списка:

public class Customer {
     List<@NotBlank(message="Address must not be blank") String> addresses;

    // standard getters, setters
}

В приведенном выше примере мы определили свойствоaddresses для классаCustomer, который содержит элементы, которые не могут быть пустымиStrings.

Обратите внимание, чтоthe @NotBlank validation applies to the String elements, and not the entire collection. Если коллекция пуста, проверка не применяется.

Давайте проверим, что если мы попытаемся добавить пустойString в списокaddresses, структура проверки вернетConstraintViolation:

@Test
public void whenEmptyAddress_thenValidationFails() {
    Customer customer = new Customer();
    customer.setName("John");

    customer.setAddresses(Collections.singletonList(" "));
    Set> violations =
      validator.validate(customer);

    assertEquals(1, violations.size());
    assertEquals("Address must not be blank",
      violations.iterator().next().getMessage());
}

Затем давайте посмотрим, как мы можем проверить элементы коллекции типаMap:

public class CustomerMap {

    private Map<@Email String, @NotNull Customer> customers;

    // standard getters, setters
}

Обратите внимание, чтоwe can add validation annotations for both the key and the value of a Map element.

Давайте проверим, что добавление записи с недействительным адресом электронной почты приведет к ошибке проверки:

@Test
public void whenInvalidEmail_thenValidationFails() {
    CustomerMap map = new CustomerMap();
    map.setCustomers(Collections.singletonMap("john", new Customer()));
    Set> violations
      = validator.validate(map);

    assertEquals(1, violations.size());
    assertEquals(
      "Must be a valid email",
      violations.iterator().next().getMessage());
}

3. Optional Значения

Ограничения проверки также могут быть применены к значениюOptional:

private Integer age;

public Optional<@Min(18) Integer> getAge() {
    return Optional.ofNullable(age);
}

Давайте создадимCustomer со слишком низким возрастом - и убедимся, что это приводит к ошибке проверки:

@Test
public void whenAgeTooLow_thenValidationFails() {
    Customer customer = new Customer();
    customer.setName("John");
    customer.setAge(15);
    Set> violations
      = validator.validate(customer);

    assertEquals(1, violations.size());
}

С другой стороны, еслиage равно нулю, то значениеOptional не проверяется:

@Test
public void whenAgeNull_thenValidationSucceeds() {
    Customer customer = new Customer();
    customer.setName("John");
    Set> violations
      = validator.validate(customer);

    assertEquals(0, violations.size());
}

4. Неуниверсальные элементы контейнера

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

Экстракторы значений - это классы, которые извлекают значения из контейнеров для проверки.

The reference implementation contains value extractors for OptionalInt, OptionalLong and OptionalDouble:

@Min(1)
private OptionalInt numberOfOrders;

В этом случае аннотация@Min применяется к обернутому значениюInteger, а не к контейнеру.

5. Пользовательские элементы контейнера

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

Таким образом, мы можем добавить аннотации проверки к элементам наших пользовательских контейнеров.

Давайте добавим новый классProfile, содержащий свойствоcompanyName:

public class Profile {
    private String companyName;

    // standard getters, setters
}

Затем мы хотим добавить свойствоProfile в классCustomer с аннотацией@NotBlank, которая проверяет, чтоcompanyName не является пустымString:

@NotBlank
private Profile profile;

Чтобы это работало, нам нужен экстрактор значений, который определяет валидацию, которая будет применяться к свойствуcompanyName, а не напрямую к объекту профиля.

Давайте добавим классProfileValueExtractor, который реализует интерфейсValueExtractor и переопределяет методextractValue():

@UnwrapByDefault
public class ProfileValueExtractor
  implements ValueExtractor<@ExtractedValue(type = String.class) Profile> {

    @Override
    public void extractValues(Profile originalValue,
      ValueExtractor.ValueReceiver receiver) {
        receiver.value(null, originalValue.getCompanyName());
    }
}

Этот класс также должен указать тип значения, извлеченного с помощью аннотации@ExtractedValue.

Также мы добавилиthe @UnwrapByDefault annotation that specifies the validation should be applied to the unwrapped value and not the container.

Наконец, нам нужно зарегистрировать класс, добавив файл с именемjavax.validation.valueextraction.ValueExtractor в каталогMETA-INF/services, который содержит полное имя нашего классаProfileValueExtractor:

org.example.javaxval.container.validation.valueextractors.ProfileValueExtractor

Теперь, когда мы проверяем объектCustomer с помощью свойстваprofile с пустымcompanyName, мы увидим ошибку проверки:

@Test
public void whenProfileCompanyNameBlank_thenValidationFails() {
    Customer customer = new Customer();
    customer.setName("John");
    Profile profile = new Profile();
    profile.setCompanyName(" ");
    customer.setProfile(profile);
    Set> violations
     = validator.validate(customer);

    assertEquals(1, violations.size());
}

Обратите внимание, что если вы используетеhibernate-validator-annotation-processor, добавление аннотации проверки к пользовательскому классу контейнера, когда она помечена как@UnwrapByDefault, приведет к ошибке компиляции в версии 6.0.2.

Этоknown issue и, вероятно, будет решено в следующей версии.

6. Заключение

В этой статье мы показали, как можно проверить несколько типов элементов контейнера с помощьюJava Bean Validation 2.0.

Вы можете найти полный исходный код примеровover on GitHub.