«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 применяется к обернутому значению Integer, а не к контейнеру.
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.baeldung.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.