Bean验证——Hibernate Validator

Hibernate Validator

一. Bean验证

1.1 什么是Bean验证

常见的业务校验场景:

1
2
3
if (employee.getFirstName() == null || employee.getFirstName().trim().length() == 0)
throw new ValidationException("validate.employee.firstName");
...

当业务规则复杂时,类似的条件判断需要很多才能实现。

Bean验证是一个Java EE的API,用来自动验证在Java bean上声明的业务逻辑。其包含一个元数据模型(一个注解的集合),其中为指定的类声明了业务规则和一个使用验证工具的API。

通过为字段、方法等添加注解的方式,指定如何在被标注的目标上使用特定的约束。

1.2 为什么选用Hibernate Validator

Hibernate Validator 5.0 是 JSR 349的参考实现,它兼容于此规范,是Bean验证实现中应用最广泛的。

Spring Framework自动为使用 Java Bean 验证的、由Spring管理的bean创建代理。它将拦截对添加了注解的方法的调用并进行适当的验证,检查使用者是否提供了有效的参数或该实现的返回值是否有效(常用注解 @javax.validation.Valid )。

二. 在Spring Framework容器中配置验证

手动验证:

1
2
3
4
5
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Employee> violations = validator.validate(employee);
if (violations.size() > 0)
throw new ConstraintViolationException(violations);

配置Spring Framework开启自动验证,使用依赖注入和代理支持,完成4个配置:

  • 验证器
  • 验证器消息的本地化
  • 方法验证处理器
  • Spring MVC表单验证

Spring Framework在Bean Validation出现之前就提供了 org.springframework.validation.Validator 接口支持对象自动验证,通过注解约束指定验证对象的工具。

验证错误会使用 org.springframework.validation.Errors 接口,而不是返回一个 Set<javax.validation.ObjectError> 。该接口提供了对一个或多个 ObjectErrorFieldError 的访问。

配置Spring Framework的验证时,需要同时实现Validator和Spring Validator,即继承了 org.springframework.validation.beanvalidation.SpringValidatorAdapter 的类,可选:

  • javax.validation.beanvalidation.CustomValidatorBean
  • javax.validation.beanvalidation.LocalValidatorFactoryBean :支持获取底层的Validator,且支持使用应用程序的其他代码用于国际化的相同MessageSource和资源包文件。
1
2
3
4
5
6
7
8
9
10
// LocalValidatorFactoryBean将自动检测到类路径上的Bean Validation实现,使用ValidatorFactory作为支持工厂,如果有多个实现类则会随机挑选。
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
// 最简单的返回
return new LocalValidatorFactoryBean();
// 动态加载的写法
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProvider(Class.forName("org.hibernate.validator.HibernateValidator"));
return validator;
}

通过LocalValidatorFactoryBean设置有效的MessageSource,自动提供一个由MessageSource作为支持的插值器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 指定错误代码和错误消息
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setCacheSeconds(-1);
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
messageSource.setBasenames("/WEB-INF/il8n/titles", "/WEB-INF/il8n/messages", "/WEB-INF/il8n/errors", "/WEB-INF/il8n/validation");
return messageSource;
}

@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(this.messageSource());
return validator;
}

使用方法验证Bean后处理器,可以在容器完成启动过程之前配置、自定义和替换配置中的bean。

三. 为bean添加约束验证注解

四. 为方法验证配置Spring bean

五. 编写自定义验证约束

六. 在客户支持应用程序中集成验证

七. 使用

7.1 使用Hibernate Validate

引入依赖

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.1.Final</version>
</dependency>

常用注解说明

注解 说明
@Length(min=,max=) 检查所属的字段的长度是否在min和max之间,只能用于字符串
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
@Max 该字段的值只能小于或等于该值
@Min 该字段的值只能大于或等于该值
@NotNull 不能为null
@NotBlank 不能为空,检查时会将空格忽略
@NotEmpty 不能为空,这里的空是指空字符串
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式

使用

需要搭配在Controller中搭配@Validated或@Valid注解一起使用,@Validated和@Valid注解区别不是很大,一般情况下任选一个即可,区别如下:

注解 @Validated @Valid
所属的包 属于org.springframework.validation.annotation包下的,是spring提供的 属于javax.validation包下,是jdk给提供的
是否支持分组和排序

虽然@Validated比@Valid更加强大,在@Valid之上提供了分组功能和验证排序功能。Hibernate-validate框架中的注解是需要加在实体中一起使用的:

  • 定义一个实体:message字段为不符合校验规则时抛出的异常信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DataSetSaveVO {
//唯一标识符为空
@NotBlank(message = "user uuid is empty")
//用户名称只能是字母和数字
@Pattern(regexp = "^[a-z0-9]+$", message = "user names can only be alphabetic and numeric")
@Length(max = 48, message = "user uuid length over 48 byte")
private String userUuid;

//数据集名称只能是字母和数字
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "data set names can only be letters and Numbers")
//文件名称过长
@Length(max = 48, message = "file name too long")
//文件名称为空
@NotBlank(message = "file name is empty")
private String name;

//数据集描述最多为256字节
@Length(max = 256, message = "data set description length over 256 byte")
//数据集描述为空
@NotBlank(message = "data set description is null")
private String description;
}
  • Controller层中的方法:在校验的实体DataSetSaveVO旁边添加@Valid或@Validated注解
1
2
3
4
@PostMapping
public ResponseVO createDataSet(@Valid @RequestBody DataSetSaveVO dataSetVO) {
return ResponseUtil.success(dataSetService.saveDataSet(dataSetVO));
}

7.2 使用commons-lang3

引入依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>

常用方法说明

方法 说明
CollectionUtils.isEmpty 判断集合是否为空,为null或者size==0,返回true
CollectionUtils.isNotEmpty 判断集合是否为非空
StringUtils.isEmpty 判断字符串是否为空
StringUtils.isNotEmpty 判断字符串是否非空
StringUtils.isBlank 判断字符串是否为空,为null或者size==0或者只存在空白字符(如” “),则返回true
StringUtils.isNotBlank 判断字符串是否为非空

测试代码:

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
//StringUtils.isEmpty
System.out.println(StringUtils.isEmpty("")); //true
System.out.println(StringUtils.isEmpty(" ")); //false
//StringUtils.isNotEmpty
System.out.println(StringUtils.isNotEmpty("")); //false

//StringUtils.isBlank
System.out.println(StringUtils.isBlank("")); //true
System.out.println(StringUtils.isBlank(" ")); //true
//StringUtils.isNotBlank
System.out.println(StringUtils.isNotBlank(" ")); //false

List<Integer> emptyList = new ArrayList<>();
List<Integer> nullList = null;
List<Integer> notEmptyList = new ArrayList<>();
notEmptyList.add(1);

//CollectionUtils.isEmpty
System.out.println(CollectionUtils.isEmpty(emptyList)); //true
System.out.println(CollectionUtils.isEmpty(nullList)); //true
System.out.println(CollectionUtils.isEmpty(notEmptyList)); //false

//CollectionUtils.isNotEmpty
System.out.println(CollectionUtils.isNotEmpty(emptyList)); //false
System.out.println(CollectionUtils.isNotEmpty(nullList)); //false
System.out.println(CollectionUtils.isNotEmpty(notEmptyList)); //true

7.3 自定义注解

当上面的方面都无法满足校验的需求以后,可以考虑使用自定义注解


参考:

🔗《Java Web高级编程—涵盖WebSockets、Spring Framework、JPA Hibernate、Spring Security》

🔗 Springboot中如何优雅进行字段校验