1. 概述
Java Bean Validation 2.0版本新增了多项特性,其中最实用的就是容器元素验证功能。这个特性充分利用了Java 8引入的类型注解(Type Annotations),因此需要Java 8或更高版本支持。
⚠️ 本文实际使用的是Jakarta Bean Validation 3.0(配合Spring Boot 3和Hibernate Validator 8.x),但核心功能与2.0版本一致。
验证注解现在可以直接应用于集合、Optional对象以及其他内置或自定义容器。关于基础配置和Maven依赖,可以参考我们的前置文章。
接下来我们将分场景演示如何验证各类容器元素:
2. 集合元素验证
可以对java.util.Iterable
、java.util.List
和java.util.Map
等集合的元素添加验证注解。
2.1 List元素验证
public class Customer {
List<@NotBlank(message="地址不能为空") String> addresses;
// 标准getter/setter
}
关键点:
@NotBlank
注解作用于String元素而非整个集合- 空集合不会触发验证
验证测试:
@Test
public void 当地址为空时验证失败() {
Customer customer = new Customer();
customer.setName("John");
customer.setAddresses(Collections.singletonList(" "));
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertEquals(1, violations.size());
assertEquals("地址不能为空", violations.iterator().next().getMessage());
}
2.2 Map元素验证
public class CustomerMap {
private Map<@Email String, @NotNull Customer> customers;
// 标准getter/setter
}
✅ Map的键和值都可以独立添加验证注解
验证测试:
@Test
public void 当邮箱无效时验证失败() {
CustomerMap map = new CustomerMap();
map.setCustomers(Collections.singletonMap("john", new Customer()));
Set<ConstraintViolation<CustomerMap>> violations = validator.validate(map);
assertEquals(1, violations.size());
assertEquals("必须是有效的邮箱", violations.iterator().next().getMessage());
}
3. Optional值验证
验证注解同样适用于Optional
包装的值:
private Integer age;
public Optional<@Min(18) Integer> getAge() {
return Optional.ofNullable(age);
}
验证场景:
- 当年龄小于18时触发验证失败
- 当年龄为null时(Optional为空)不触发验证
@Test
public void 当年龄过小时验证失败() {
Customer customer = new Customer();
customer.setName("John");
customer.setAge(15);
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertEquals(1, violations.size());
}
@Test
public void 当年龄为null时验证通过() {
Customer customer = new Customer();
customer.setName("John");
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertEquals(0, violations.size());
}
4. 非泛型容器验证
对于非泛型容器,只要存在带@UnwrapByDefault
注解的值提取器(Value Extractor),同样可以应用验证。
Hibernate Validator内置了这些值提取器:
OptionalInt
OptionalLong
OptionalDouble
示例:
@Min(1)
private OptionalInt numberOfOrders;
这里@Min
注解实际作用于包装的int
值,而非容器本身。
5. 自定义容器验证
除了内置容器,我们还可以为自定义容器创建值提取器。
5.1 创建自定义容器
public class Profile {
private String companyName;
// 标准getter/setter
}
5.2 添加验证注解
@NotBlank
private Profile profile;
5.3 实现值提取器
@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
指定验证应用于解包后的值
5.4 注册值提取器
在META-INF/services
目录创建文件:
javax.validation.valueextraction.ValueExtractor
内容:
com.example.validation.ProfileValueExtractor
5.5 验证测试
@Test
public void 当公司名为空时验证失败() {
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版本会导致编译错误(已知问题)。
6. 总结
本文展示了使用Jakarta Bean Validation 3.0验证各类容器元素的完整方案:
✅ 核心能力:
- 集合元素验证(List/Map)
- Optional值验证
- 非泛型容器验证
- 自定义容器验证
完整示例代码可在GitHub仓库获取。