Validating Container Elements with Bean Validation 2.0 – 用Bean Validation 2.0验证容器元素

最后修改: 2017年 10月 5日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The 2.0 Version of the Java Bean Validation specification adds several new features, among which is the possibility to validate elements of containers.

2.0版的Java Bean验证规范增加了一些新的功能,其中包括验证容器元素的可能性。

This new functionality takes advantage of type annotations introduced in Java 8. Therefore it requires Java version 8 or higher to work.

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

Validation annotations can be added to containers such as collections, Optional objects, and other built-in as well as custom containers.

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

For an introduction to Java Bean Validation and how to setup the Maven dependencies we need, check out our previous article here.

关于Java Bean验证以及如何设置我们需要的Maven依赖的介绍,请查看我们的前文

In the following sections, we’ll focus on validating elements of each type of container.

在下面的章节中,我们将重点讨论验证每种类型的容器的元素。

2. Collection Elements

2.收集元素

We can add validation annotations to elements of collections of type java.util.Iterable, java.util.List and java.util.Map.

我们可以为java.util.Iterablejava.util.Listjava.util.Map类型的集合的元素添加验证注释。

Let’s see an example of validating the elements of a List:

让我们看一个验证List元素的例子。

public class Customer {    
     List<@NotBlank(message="Address must not be blank") String> addresses;
    
    // standard getters, setters 
}

In the example above, we’ve defined an addresses property for a Customer class, which contains elements that cannot be empty Strings.

在上面的例子中,我们为一个Customer类定义了一个addresses属性,该类包含的元素不能是空的Strings

Note that the @NotBlank validation applies to the String elements, and not the entire collection. If the collection is empty, then no validation is applied.

注意,@NotBlank验证适用于String元素,而不是整个集合。如果集合是空的,那么就不应用验证。

Let’s verify that if we attempt to add an empty String to the addresses list, the validation framework will return a ConstraintViolation:

让我们验证一下,如果我们试图在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());
}

Next, let’s see how we can validate the elements of a collection of type Map:

接下来,让我们看看如何验证Map类型的集合中的元素。

public class CustomerMap {
    
    private Map<@Email String, @NotNull Customer> customers;
    
    // standard getters, setters
}

Notice that we can add validation annotations for both the key and the value of a Map element.

请注意,我们可以为Map元素的键和值都添加验证注释

Let’s verify that adding an entry with an invalid email will result in a validation error:

让我们验证一下,添加一个无效的电子邮件的条目会导致验证错误。

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

3.可选的价值

Validation constraints can also be applied to an Optional value:

验证约束也可以应用于Optional值。

private Integer age;

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

Let’s create a Customer with an age that is too low – and verify that this results in a validation error:

让我们创建一个年龄过低的客户–并验证这是否会导致一个验证错误。

@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());
}

On the other hand, if the age is null, then the Optional value is not validated:

另一方面,如果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. Non-Generic Container Elements

4.非通用的容器元素

Besides adding annotations for type arguments, we can also apply validation to non-generic containers, as long as there is a value extractor for the type with the @UnwrapByDefault annotation.

除了为类型参数添加注解,我们还可以对非通用容器应用验证,只要有一个带有@UnwrapByDefault注解的类型的值提取器。

Value extractors are the classes that extract the values from the containers for validation.

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

The reference implementation contains value extractors for OptionalInt, OptionalLong and OptionalDouble:

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

@Min(1)
private OptionalInt numberOfOrders;

In this case, the @Min annotation applies to the wrapped Integer value, and not the container.

在这种情况下, @Min 注解适用于被包装的Integer值,而不是容器。

5. Custom Container Elements

5.自定义容器元素

In addition to the built-in value extractors, we can also define our own and register them with a container type.

除了内置的值提取器外,我们还可以定义我们自己的值提取器,并将其与容器类型一起注册。

In this way, we can add validation annotations to elements of our custom containers.

通过这种方式,我们可以为我们的自定义容器的元素添加验证注释。

Let’s add a new Profile class that contains a companyName property:

让我们添加一个新的Profile类,包含一个companyName属性。

public class Profile {
    private String companyName;
    
    // standard getters, setters 
}

Next, we want to add a Profile property in the Customer class with a @NotBlank annotation – which verifies the companyName is not an empty String:

接下来,我们要在Customer类中添加一个带有@NotBlank注解的Profile属性–它验证companyName不是一个空的String

@NotBlank
private Profile profile;

For this to work, we need a value extractor that determines the validation to be applied to the companyName property and not the profile object directly.

要做到这一点,我们需要一个值提取器,确定验证要应用于companyName属性,而不是直接应用于profile对象。

Let’s add a ProfileValueExtractor class that implements the ValueExtractor interface and overrides the extractValue() method:

让我们添加一个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());
    }
}

This class also need to specify the type of the value extracted using the @ExtractedValue annotation.

这个类还需要使用@ExtractedValue注解来指定提取的值的类型。

Also, we’ve added the @UnwrapByDefault annotation that specifies the validation should be applied to the unwrapped value and not the container.

此外,我们还添加了@UnwrapByDefault注解,指定验证应该应用于解包的值而不是容器

Finally, we need to register the class by adding a file called javax.validation.valueextraction.ValueExtractor to the META-INF/services directory, which contains the full name of our ProfileValueExtractor class:

最后,我们需要添加一个名为javax.validation.valueextraction.ValueExtractor的文件到META-INF/services目录,其中包含我们的ProfileValueExtractor类的全名,以此来注册该类。

org.baeldung.javaxval.container.validation.valueextractors.ProfileValueExtractor

Now, when we validate a Customer object with a profile property with an empty companyName, we will see a validation error:

现在,当我们验证一个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<ConstraintViolation<Customer>> violations
     = validator.validate(customer);
 
    assertEquals(1, violations.size());
}

Note that if you are using hibernate-validator-annotation-processor, adding a validation annotation to a custom container class, when it’s marked as @UnwrapByDefault, will result in a compilation error in version 6.0.2.

请注意,如果你使用hibernate-validator-annotation-processor,向自定义容器类添加验证注解,当它被标记为@UnwrapByDefault时,将导致6.0.2版的编译错误。

This is a known issue and will likely be resolved in a future version.

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

6. Conclusion

6.结论

In this article, we’ve shown how we can validate several types of container elements using Java Bean Validation 2.0.

在这篇文章中,我们展示了如何使用Java Bean Validation 2.0.来验证几种类型的容器元素。

You can find the full source code of the examples over on GitHub.

你可以在GitHub上找到这些例子的完整源代码