Java中@Valid子对象注释

在本教程中,我们将了解如何使用@Valid注释来验证对象及其嵌套的子对象。

当传入数据是基本数据类型(例如整数或字符串)时,验证传入数据可能很简单。但是,当传入信息是对象(特别是对象图)时,验证就比较困难了。幸运的是,@Valid注释简化了嵌套子对象的验证。

什么是@Valid注解?
@Valid注释来自Jakarta Bean Validation规范,标记需要验证的特定参数。

使用此注解可确保传递给方法或存储在字段中的数据符合指定的验证规则。这有助于我们提高数据完整性和一致性。

当在JavaBean的字段或方法上使用时,它会触发所有定义的约束检查。Bean Validation API中最常用的一些约束包括@NotNull、@NotBlank、@NotEmpty、@Size、@Email、@Pattern 等。

如何在子对象上使用@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;
    // omitted constructors, getters and setters
}
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;
    // omitted constructors, getters and setters
}

之后,我们将使用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 验证可以与Spring和Jakarta EE等框架完美配合。通过在控制器类的方法参数上使用注释,我们甚至可以在进入控制器方法之前执行验证,这对于保持数据一致性非常有用。

了解对象图验证
现在我们已经了解了如何使用@Valid,让我们更好地理解它为什么以这种方式工作。在对象有其他嵌套对象的情况下,我们必须应用一种称为对象图验证的机制。

此机制可验证对象图中相关对象的完整结构。所有使用@Valid注释的子对象(及其子对象)在其父对象为 时都会进行验证。换句话说,验证会在整个图中递归应用。

经过此图的遍历,我们得到了ConstraintViolations集合,其中包含来自嵌套对象的所有组合验证违规。

由于我们以递归方式验证图中的每个对象,因此我们可能会遇到循环引用问题,即对象以循环方式相互引用。这可能会使我们陷入无限循环,不断重复验证相同的对象。

幸运的是,Jakarta Bean Validation 包含定义验证路径的概念,该路径被描述为从根对象开始的@Valid关联序列。实现会跟踪当前路径中已验证的每个实例,从根对象开始。如果同一个实例在给定的导航路径中出现多次,验证例程将忽略它,从而防止无限循环。

子对象上的注解使用
现在我们已经了解了如何使用@Valid注释以及它在底层的工作原理,让我们来看看所有可以使用它的地方。我们将研究如何在容器对象中的嵌套实例、集合和类型参数上使用@Valid。

1. 使用@Valid验证嵌套实例
验证嵌套实例的一种方法是使用字段访问策略,这与我们在上一个示例中验证项目内嵌套的User对象的方法相同。简单地说,我们用@Valid注释修饰字段,然后将该实例添加到导航路径中:

@Valid
private User owner;

类似地,验证嵌套实例的另一种方法是使用属性访问策略,这意味着我们可以将@Valid放在 getter 方法上,以此方式访问属性的状态:

@Valid
public User getOwner() {
    return owner;
}

2. 使用@Valid验证可迭代对象
集合、数组或java.lang.Iterebale接口的任何其他实现都符合@Valid注释的条件。如果我们在这种情况下进行注释,我们将按照相同的规则对Iterable的每个元素应用验证。

重要的是要知道,如果集合是java.util.Map接口的实现,则只会验证值。我们必须专门注释键以触发对它们的验证。

例如,让我们检查一个同时验证键和值的Map :

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

3. 在容器对象和类型参数上使用注释
在容器对象和类型参数上应用注解非常相似,让我们首先看一下如何操作:

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

第一个例子展示了在容器上使用注解,而第二个例子则直接在类型参数上使用注解。在这种情况下,没有区别,它们都按我们预期的方式工作。一般来说,我们应该避免在两个地方使用它,因为这可能会导致容器元素被验证两次。

我们可以看到,在这些情况下使用注释是灵活的,但它们并不总是按照我们想要的方式工作。在我们有嵌套通用容器的情况下,为了验证容器的内容,我们必须在内部容器的类型引用上应用注释。

让我们看一个List嵌套在Map中的例子:

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


结论
在本文中,我们了解了什么是@Valid注释,如何使用它对子对象执行验证,以及对象图验证如何工作。

@Valid注释是一个功能强大的工具,我们可以在不同的地方使用它来确保事物按预期进行验证。它很棒,因为它会自动检查图中的每个已验证对象,使我们的工作更轻松。