Contents

Java Bean Validation 2.0 简介

1. 概述

Java Bean Validation规范的 2.0 版本增加了几个新特性,其中包括验证容器元素的可能性。

这个新功能利用了 Java 8 中引入的类型注释。因此它需要 Java 8 或更高版本才能工作。

**可以将验证注释添加到容器中,例如集合、Optional 对象以及其他内置和自定义容器。

有关Java Bean Validation的介绍以及如何设置我们需要的Maven依赖项,请在此处查看我们之前的文章

在接下来的部分中,我们将专注于验证每种容器类型的元素。

2.集合元素

我们可以为java.util.Iterablejava.util.Listjava.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*注释的类型的值提取器。

值提取器是从容器中提取值以进行验证的类。

参考实现包含OptionalIntOptionalLongOptionalDouble的值提取器:

@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 中的编译错误。

这是一个已知问题 ,可能会在未来的版本中得到解决。