Перейти к основному содержимому

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

· 5 мин. чтения

1. Обзор

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

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

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

Для ознакомления с проверкой Java Bean Validation и тем, как настроить необходимые нам зависимости Maven , ознакомьтесь с нашей предыдущей статьей здесь .

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

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
}

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

Обратите внимание, что проверка @NotBlank применяется к элементам String , а не ко всей коллекции . Если коллекция пуста, проверка не применяется.

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

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

customer.setAddresses(Collections.singletonList(" "));
Set<ConstraintViolation<Customer>> 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
}

Обратите внимание, что мы можем добавить аннотации проверки как для ключа, так и для значения элемента Map .

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

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

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

3. Дополнительные значения

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

private Integer age;

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

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

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

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

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

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

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

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

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

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

`` Эталонная реализация содержит экстракторы значений дляOptionalInt , OptionalLong иOptionalDouble :

@Min(1)
private OptionalInt numberOfOrders;

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

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

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

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

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

public class Profile {
private String companyName;

// standard getters, setters
}

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

@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 .

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

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

org.foreach.javaxval.container.validation.valueextractors.ProfileValueExtractor

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

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

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

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

Это известная проблема , которая, скорее всего, будет решена в следующей версии.

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

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

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