1. 引言

本文将探讨如何使用 @Valid 注解验证对象及其嵌套的子对象。

当验证基础数据类型(如整数或字符串)时相对简单,但处理复杂对象图时就会棘手得多。幸运的是,*@Valid* 注解能简化嵌套子对象的验证过程。

2. @Valid 注解是什么?

@Valid 注解源自 Jakarta Bean Validation 规范,用于标记需要验证的参数。

该注解确保传递给方法或存储在字段中的数据符合指定的验证规则,有助于维护数据完整性和一致性。

当应用于 JavaBean 的字段或方法时,会触发所有定义的约束检查。常用约束包括 @NotNull、@NotBlank、@NotEmpty、@Size、@Email、@Pattern 等。

3. 如何在子对象上使用 @Valid 注解

首先定义验证规则并应用约束注解。接着创建表示项目的类,其中包含一个嵌套的 User 对象,我们将用 @Valid 注解修饰它

public class User {

    @NotBlank(message = "User name must be present")
    @Size(min = 3, max = 50, message = "User name size not valid")
    private String name;

    @NotBlank(message = "User email must be present")
    @Email(message = "User email format is incorrect")
    private String email;

    // 省略构造方法、getter和setter

}

public class Project {

    @NotBlank(message = "Project title must be present")
    @Size(min = 3, max = 20, message = "Project title size not valid")
    private String title;

    @Valid
    private User owner;

    // 省略构造方法、getter和setter

}

然后通过 Validator 实例的 validate() 方法执行验证。通过以下测试验证子对象:

@Test
public void whenInvalidProjectAndUser_thenAssertConstraintViolations() {
    Project project = new Project(null);
    project.setOwner(new User(null, "invalid-email"));

    List<String> messages = validate(project);

    assertEquals(3, messages.size());
    assertTrue(messages.contains("Project title must be present"));
    assertTrue(messages.contains("User name must be present"));
    assertTrue(messages.contains("User email format is incorrect"));
}

private List<String> validate(Project project) {
    return validator.validate(project)
      .stream()
      .map(ConstraintViolation::getMessage)
      .collect(Collectors.toList());
}

此外,使用 @Valid 的 Bean 验证与 SpringJakarta EE 等框架完美集成。在控制器方法参数上使用该注解,可在进入方法前完成验证,是保持数据一致性的利器。

4. 理解对象图验证

了解了 @Valid 的用法后,我们深入探讨其工作原理。当对象包含其他嵌套对象时,需要应用对象图验证机制。

该机制验证对象图中所有关联对象的完整结构。所有被 @Valid 注解的子对象(及其后代)会在父对象验证时被递归验证,即验证会遍历整个对象图。

遍历结果返回包含所有嵌套对象验证错误的 ConstraintViolations 集合。

由于递归验证每个对象,循环引用可能导致无限验证循环。幸运的是,Jakarta Bean Validation 通过验证路径概念解决此问题——验证路径是从根对象开始的 @Valid 关联序列,实现会记录当前路径中已验证的实例。若同一实例在路径中重复出现,验证例程会自动跳过,从而避免无限循环。

5. 子对象上的注解使用场景

现在我们掌握 @Valid 的用法和原理,接下来看看它的适用场景:嵌套实例、集合和容器对象的类型参数。

5.1. 使用 @Valid 验证嵌套实例

验证嵌套实例有两种方式:

  • 字段访问策略:直接在字段上添加 @Valid(如前例中的 User 对象):
    @Valid
    private User owner;
    
  • 属性访问策略:在 getter 方法上添加 @Valid
    @Valid
    public User getOwner() {
        return owner;
    }
    

5.2. 使用 @Valid 验证可迭代对象

集合、数组或任何 java.lang.Iterable 实现都支持 @Valid 注解。此时会验证 Iterable 中的每个元素

⚠️ 需注意:对于 java.util.Map 实现,默认只验证值。若需验证键,必须单独注解:

private Map<@Valid User, @Valid Task> assignedTasks;

5.3. 在容器对象和类型参数上使用注解

在容器和类型参数上使用注解效果相似:

@Valid 
private List<Task> tasks;
    
private List<@Valid Task> tasks;

两种写法效果相同,但应避免同时使用,否则会导致容器元素被重复验证。

✅ 嵌套泛型容器时,必须在内层容器的类型引用上添加注解才能验证内容。例如嵌套在 Map 中的 List

private Map<String, List<@Valid Task>> taskByType;

6. 结论

本文介绍了 @Valid 注解的概念、在子对象上的使用方法及对象图验证原理。

@Valid 是强大的验证工具,能自动递归验证对象图中的所有对象,极大简化开发工作

完整示例代码见 GitHub


原始标题:@Valid Annotation on Child Objects | Baeldung