Проверка контейнерных элементов с помощью 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.