1. 概述
Bean Validation 是一个标准的校验规范,它允许我们通过注解形式声明一组约束来轻松校验领域对象。
虽然使用像 Hibernate Validator 这样的 Bean Validation 实现整体上非常直观,但一些约束在实现细节上存在细微却重要的差异,值得深入探讨。
在本篇文章中,我们将重点分析 @NotNull、*@NotEmpty* 和 @NotBlank 三者之间的区别。
2. Maven 依赖配置
为了快速搭建一个可运行的测试环境,验证这三种注解的行为,首先我们需要引入必要的 Maven 依赖。
这里我们使用 Hibernate Validator —— Bean Validation 的参考实现,用于校验我们的领域对象。
以下是 pom.xml 中的相关依赖配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
该依赖会自动引入 Hibernate Validator 相关的依赖。如果你没有使用 Spring Boot,可以直接引入 Hibernate Validator:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
我们还会在单元测试中使用 JUnit 和 AssertJ,请确保你已在 Maven Central 上获取最新版本的 hibernate-validator、GlassFish 的 EL 实现、junit 和 assertj-core。
3. @NotNull 约束详解
接下来,我们先定义一个简单的 UserNotNull
领域类,并对其中的 name
字段使用 @NotNull 注解:
public class UserNotNull {
@NotNull(message = "Name may not be null")
private String name;
// 标准构造器 / getter / toString 省略
}
现在我们来探究一下 @NotNull 的实际行为。
为此,我们编写一个简单的单元测试,校验几种不同的情况:
@BeforeClass
public static void setupValidatorInstance() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
public void whenNotNullName_thenNoConstraintViolations() {
UserNotNull user = new UserNotNull("John");
Set<ConstraintViolation<UserNotNull>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(0);
}
@Test
public void whenNullName_thenOneConstraintViolation() {
UserNotNull user = new UserNotNull(null);
Set<ConstraintViolation<UserNotNull>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@Test
public void whenEmptyName_thenNoConstraintViolations() {
UserNotNull user = new UserNotNull("");
Set<ConstraintViolation<UserNotNull>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(0);
}
✅ 如你所料,*@NotNull* 不允许字段为 null
,但允许为空字符串。
我们可以通过查看 NotNullValidator 类 的 isValid()
方法来理解它的实现逻辑,该方法非常简单:
public boolean isValid(Object object) {
return object != null;
}
⚠️ 总结:*使用 @NotNull 约束的字段(如 CharSequence、Collection、Map* 或 Array)不能为 null
,但可以为空(如空字符串)**。
4. @NotEmpty 约束详解
接下来我们定义 UserNotEmpty
类,并使用 @NotEmpty 注解:
public class UserNotEmpty {
@NotEmpty(message = "Name may not be empty")
private String name;
// 标准构造器 / getter / toString 省略
}
我们通过测试不同值的 name
字段来验证其行为:
@Test
public void whenNotEmptyName_thenNoConstraintViolations() {
UserNotEmpty user = new UserNotEmpty("John");
Set<ConstraintViolation<UserNotEmpty>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(0);
}
@Test
public void whenEmptyName_thenOneConstraintViolation() {
UserNotEmpty user = new UserNotEmpty("");
Set<ConstraintViolation<UserNotEmpty>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@Test
public void whenNullName_thenOneConstraintViolation() {
UserNotEmpty user = new UserNotEmpty(null);
Set<ConstraintViolation<UserNotEmpty>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@NotEmpty 注解不仅检查字段是否为 null
,还会检查其长度或大小是否大于 0。它内部使用了 @NotNull 的校验逻辑,并增加了对长度的判断。
✅ 总结:使用 @NotEmpty 约束的字段必须非 null
,且其长度/大小必须大于 0。
⚠️ 此外,我们还可以将 @NotEmpty 与 @Size 配合使用,进一步限制字段的长度范围:
@NotEmpty(message = "Name may not be empty")
@Size(min = 2, max = 32, message = "Name must be between 2 and 32 characters long")
private String name;
5. @NotBlank 约束详解
同样地,我们也可以对字段使用 @NotBlank 注解:
public class UserNotBlank {
@NotBlank(message = "Name may not be blank")
private String name;
// 标准构造器 / getter / toString 省略
}
接着我们编写测试用例,验证其行为:
@Test
public void whenNotBlankName_thenNoConstraintViolations() {
UserNotBlank user = new UserNotBlank("John");
Set<ConstraintViolation<UserNotBlank>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(0);
}
@Test
public void whenBlankName_thenOneConstraintViolation() {
UserNotBlank user = new UserNotBlank(" ");
Set<ConstraintViolation<UserNotBlank>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@Test
public void whenEmptyName_thenOneConstraintViolation() {
UserNotBlank user = new UserNotBlank("");
Set<ConstraintViolation<UserNotBlank>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@Test
public void whenNullName_thenOneConstraintViolation() {
UserNotBlank user = new UserNotBlank(null);
Set<ConstraintViolation<UserNotBlank>> violations = validator.validate(user);
assertThat(violations.size()).isEqualTo(1);
}
@NotBlank 注解使用的是 NotBlankValidator 类,其内部会对字符串进行 trim()
处理,并判断长度是否大于 0:
public boolean isValid(
CharSequence charSequence,
ConstraintValidatorContext constraintValidatorContext) {
if (charSequence == null ) {
return false;
}
return charSequence.toString().trim().length() > 0;
}
✅ 总结:使用 @NotBlank 约束的字段必须非 null
,且去除前后空格后的长度必须大于 0。
⚠️ 换句话说,*@NotBlank* 是最严格的字符串约束,连只包含空格的字符串都不允许。
6. 三者对比一览
到目前为止,我们已经分别介绍了这三种注解的使用方式和行为。下面通过一个对比表格来快速总结它们的区别:
注解 | 是否允许 null |
是否允许空字符串 "" |
是否允许纯空格字符串 " " |
---|---|---|---|
@NotNull | ❌ 不允许 | ✅ 允许 | ✅ 允许 |
@NotEmpty | ❌ 不允许 | ❌ 不允许 | ✅ 允许 |
@NotBlank | ❌ 不允许 | ❌ 不允许 | ❌ 不允许 |
✅ 简单粗暴总结:
- @NotNull:只要不是
null
就行,哪怕是空字符串。 - @NotEmpty:不能为空,长度必须大于 0。
- @NotBlank:不能是空白字符,必须有“实际内容”。
7. 结论
本文我们详细分析了 Bean Validation 中 @NotNull、*@NotEmpty* 和 @NotBlank 三种注解的使用方式和行为差异,并通过示例代码和测试验证了它们的校验逻辑。
如需查看本文中的完整代码示例,可访问 GitHub 项目地址。