Java Bean Validation 2.0 简介
1. 概述
Java Bean Validation规范的 2.0 版本增加了几个新特性,其中包括验证容器元素的可能性。
这个新功能利用了 Java 8 中引入的类型注释。因此它需要 Java 8 或更高版本才能工作。
**可以将验证注释添加到容器中,例如集合、Optional 对象以及其他内置和自定义容器。
有关Java Bean Validation的介绍以及如何设置我们需要的Maven依赖项,请在此处查看我们之前的文章 。
在接下来的部分中,我们将专注于验证每种容器类型的元素。
2.集合元素
我们可以为java.util.Iterable、java.util.List和java.util.Map类型的集合元素添加验证注释。
让我们看一个验证 List 元素的示例:
public class Customer {
List<@NotBlank(message="Address must not be blank") String> addresses;
// standard getters, setters
}
在上面的示例中,我们为Customer类定义了address属性,其中包含不能为空Strings的元素。
请注意,@NotBlank验证适用于String元素,而不是整个 collection**。如果集合为空,则不应用验证。
让我们验证一下,如果我们尝试向addresses 列表添加一个空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. 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<ConstraintViolation<Customer>> violations
= validator.validate(customer);
assertEquals(1, violations.size());
}
另一方面,如果*age 为空,则不验证Optional *:
@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. 自定义容器元素
除了内置的值提取器,我们还可以定义我们自己的并将它们注册到容器类型中。
通过这种方式,我们可以为自定义容器的元素添加验证注释。
让我们添加一个包含companyName属性的新Profile类:
public class Profile {
private String companyName;
// standard getters, setters
}
接下来,我们想在Customer类中添加一个带有*@NotBlank注释的Profile属性——它验证companyName不是空的String*:
@NotBlank
private Profile profile;
为此,我们需要一个值提取器来确定要应用于companyName属性而不是直接应用于配置文件对象的验证。
让我们添加一个实现ValueExtractor接口并覆盖extractValue()方法的ProfileValueExtractor类:
@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*注释,指定验证应该应用于展开的值而不是容器。
最后,我们需要通过在META-INF/services目录中添加一个名为javax.validation.valueextraction.ValueExtractor的文件来注册该类,该目录包含我们ProfileValueExtractor类的全名:
com.blogdemo.javaxval.container.validation.valueextractors.ProfileValueExtractor
现在,当我们使用带有空companyName的配置文件属性验证Customer对象时,我们将看到验证错误:
@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 中的编译错误。
这是一个已知问题 ,可能会在未来的版本中得到解决。