Java Bean Validation

1. 概述

本文主要介绍标准验证框架JSR 380(也称为Bean Validation 2.0)对于JavaBean的验证功能。

在应用程序中验证用户输入是一个非常常见的要求,Java Bean Validation框架已经成为处理这种逻辑的事实上的标准。

2. JSR 380

JSR 380是用于 JavaBean 验证的规范,也是JavaEE的和的JavaSE的一部分,它通过一系列的注解来确保 Bean 符合某些规则,比如 @NotNull@Min,和@Max

JSR380 需要Java 8或更高版本,并利用Java 8中添加的新功能(如 type annotation),并支持OptionalLocalDate等新类型,有关规范的完整信息,可以查阅JSR 380

3. 依赖

我们可以采用使用Maven或者 Gradle 来配置相应的依赖,本文示例中都采用 Maven。

3.1 Validation API

根据JSR 380规范,validation-api依赖于标准验证API,如下配置所示:

1
2
3
4
5
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.0.Final</version>
</dependency>

3.2 Validation API 参考实现

Hibernate Validator是Validation API的参考实现,要使用它,我们必须添加以下依赖项:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.0.2.Final</version>
</dependency>

注意:hibernate-validator完全独立于Hibernate的 ORM hibernate-validator 并不会将 ORM 相关的依赖添加到项目中,可以放心的使用。

3.3 表达式语言依赖关系

JSR 380为变量插值提供支持,允许在不符合规则的错误消息中使用表达式。要解析这些表达式,我们必须在表达式语言API和该API的实现上添加依赖项。GlassFish提供参考实现:

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>

<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.6</version>
</dependency>

如果未添加这些JAR,您将在运行时收到错误消息,如下所示:

HV000183:无法加载’javax.el.ExpressionFactory’。检查您是否在类路径上具有EL依赖项,或使用ParameterMessageInterpolator

4. 使用 Validation Annotations

我们将使用User bean作为主要示例,并为其添加一些简单的验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;

public class User {

@NotNull(message = "Name cannot be null")
private String name;

@AssertTrue
private boolean working;

@Size(min = 10, max = 200, message
= "About Me must be between 10 and 200 characters")
private String aboutMe;

@Min(value = 18, message = "Age should not be less than 18")
@Max(value = 150, message = "Age should not be greater than 150")
private int age;

@Email(message = "Email should be valid")
private String email;

// standard setters and getters

示例中使用的所有注释都是标准的JSR注释:

  • @NotNull - 验证属性值不为 null
  • @AssertTrue - 验证属性值是否为 true
  • @Size - 验证带注属性长度/大小是否在 minmax之间,可以应用于 StringCollectionMap和数组属性
  • @Min - 验证属性的值是否小于 value属性
  • @Max - 验证属性的值是否大于 value属性
  • @Email - 验证属性是否为有效的电子邮件地址

某些注释接受其他属性,但message 属性对所有属性都是通用的。这是当相应属性的值未通过验证时通常会提示的信息。

您还可以在JSR中找到的一些其他注解,如:

  • @NotEmpty - 验证属性不为null或为空; 可以应用于 StringCollectionMapArray
  • @NotBlank - 只能应用于文本值,验证该属性不是null或空格
  • @Positive@PositiveOrZero - 适用于数值,验证它们是严格正数(包括0)
  • @Negative@NegativeOrZero - 适用于数值,验证它们是严格为负数(包括0)
  • @Past@PastOrPresent - 适用于日期值(包括Java 8中添加的日期类型),验证它们是过去(包括现在);
  • *@Future@FutureOrPresent* - 适用于日期值(包括Java 8中添加的日期类型),验证它们是将来(包括现在);

验证注解也可以应用于集合的元素

1
List<@NotBlank String> preferences;

在这种情况下,所有添加到 preferences 中的值都将进行 @NotBlank 的校验。

同时,该规范还支持 Java 8中的新Optional类型,如:

1
2
3
4
5
rivate LocalDate dateOfBirth;

public Optional<@Past LocalDate> getDateOfBirth() {
return Optional.of(dateOfBirth);
}

在这里,验证框架将自动解包LocalDate值并对其进行验证。

5. 程序验证

一些框架( 例如Spring)只需使用注释就可以通过简单的方法触发验证过程。同时,我们也可以手动以以编程方式进行设置与交互:

1
2
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

要验证bean,我们必须首先拥有Validator对象,该对象使用ValidatorFactory构造。

5.1 定义Bean

我们现在要设置这个无效用户——name = null

1
2
3
4
User user = new User();
user.setWorking(true);
user.setAboutMe("Its all about me!");
user.setAge(50);

5.2 验证Bean

现在我们有了一个Validator,我们可以通过将它传递给validate方法来验证我们的bean 。任何违反User对象中定义的约束的行为都将作为Set返回。

1
Set<ConstraintViolation<User>> violations = validator.validate(user);

通过迭代 ConstraintViolation,我们可以使用getMessage方法获取所有违反规则的提示信息:

1
2
3
for (ConstraintViolation<User> violation : violations) {
log.error(violation.getMessage());
}

在我们的示例中(ifNameIsNull_nameValidationFails),该集合将包含一个ConstraintViolation,消息” Name not not null “。

6. 结论

本文重点介绍了标准 JSR380 的相关规约,并通过注解演示了如何设置 JavaBean 的校验规则,以及通过编程的方式获取所有违反规则的错误提示信息。