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 验证与 Spring 和 Jakarta 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。