SpringBoot校验(validation)

元编程

一个健壮的系统都要对外部提交的数据进行完整性、合法性的校验。

校验是我们程序开发中必不可少的过程。

即使开发一个不面对最终用户的工具包,也需要对传入的数据进行缜密的校验来防止引发底层难以追踪的问题。

后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。

不使用Bean Validation校验数据的代码基本都是靠大量的 if-else.所以我这里学习使用了 注解方式实现数据校验.

使用

SpringBoot 中的 bean validation 是集成了hibernate-validator tomcat-embed-el

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

简单的校验

@Valid:常见用在方法,类中字段上进行校验

@Validated:是spring提供的对@Valid的封装,常见用在方法上进行校验:用在类型、方法和方法参数上。但不能用于成员属性(field)

BindingResult:是验证是否错误

Model中

1
2
@Range(max = 150, min = 1, message = "年龄范围应该在1-150内。")
private Integer age;

Controller中

1
2
3
4
5
6
7
8
@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}

绑定多个校验对象

1
2
3
4
5
6
7
8
@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result,@RequestBody @Valid AppUser appUser2,BindingResult result2){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}

部分标签

Bean Validation 中内置的 constraint

注解 作用
@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

注解 作用
@Email 被注释的元素必须是电子邮箱地址
@Length(min=, max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=, max=) 被注释的元素必须在合适的范围内
@NotBlank 被注释的字符串的必须非空
@URL(protocol=,host=, port=,regexp=, flags=) 被注释的字符串必须是一个有效的url

官方详细

中文详细

容易记错的

@NotNull 任何对象的value不能为null

@NotEmpty 集合对象的元素不为0,即集合不为空,也可以用于字符串不为null

@NotBlank 只能用于字符串不为null,并且字符串trim()以后length要大于0

resource 下新建错误信息配置文件

在resource 目录下新建提示信息配置文件 ValidationMessages.properties

文件中的格式为message=ASCII.

中文的ascii码

hibernate的校验模式

1、普通模式(默认是这个模式)

  普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

2、快速失败返回模式

  快速失败返回模式(只要有一个验证失败,则返回)

两种验证模式配置方式

参考官方文档

failFast:true 快速失败返回模式 false 普通模式

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation
.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
}
}

和 (hibernate.validator.fail_fast:true 快速失败返回模式 false 普通模式)

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}

手动验证Model

1
2
3
4
5
AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser);
for (ConstraintViolation<AppUser> model : violationSet) {
System.out.println(model.getMessage());
}

分组校验(区分新增和修改的规则)

简单使用

场景

新增用户信息和修改用户信息所需要验证的字段是不同的.

1
2
3
4
5
public interface AppUserVaildC {
}

public interface AppUserVaildU {
}

Model中

Default 是默认分组.

1
2
3
4
5
6
@Range(min = 0,max = 100,message = "年龄必须在[0,100]",groups={Default.class})
/**年龄*/
private Integer age;
@Range(min = 0,max = 2,message = "性别必须在[0,2]",groups = {AppUserVaildC.class})
/**性别 0:未知;1:男;2:女*/
private Integer sex;

Controller中使用

1
2
3
4
5
6
7
8
@PostMapping("save")
public void v1(@RequestBody @Validated({AppUserVaildC.class, AppUserVaildU.class}) AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}

普通使用

1
2
3
4
5
AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser,AppUserVaildC,AppUserVaildU);
for (ConstraintViolation<AppUser> model : violationSet) {
System.out.println(model.getMessage());
}

组序列

除了按组指定是否验证之外,还可以指定组的验证顺序,前面组验证不通过的,后面组不进行验证

1
2
3
@GroupSequence({AppUserVaildC.class, AppUserVaildU.class, Default.class})
public interface GroupOrder {
}

Controller中使用

1
2
3
4
5
6
7
8
@PostMapping("save")
public void v1(@RequestBody @Validated({GroupOrder.class}) AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}

普通使用

1
2
3
4
5
AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser,GroupOrder);
for (ConstraintViolation<AppUser> model : violationSet) {
System.out.println(model.getMessage());
}

自定义验证

下面是一个自定义大小写的验证

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
28
29
30
31
32
33
34
35
36
37
38
39
public enum CaseMode {
UPPER,
LOWER;
}


@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
String message() default "";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

CaseMode value();
}


public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
public void initialize(CheckCase checkCase) {
this.caseMode = checkCase.value();
}

public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (s == null) {
return true;
}

if (caseMode == CaseMode.UPPER) {
return s.equals(s.toUpperCase());
} else {
return s.equals(s.toLowerCase());
}
}
}

Model中

1
2
3
4
5
6
@Range(value = CaseMode.LOWER ,message = "年必须是小写",groups={Default.class})
/**年龄*/
private String loginName;
@Range(min = 0,max = 2,message = "性别必须在[0,2]",groups = {AppUserVaildC.class})
/**性别 0:未知;1:男;2:女*/
private Integer sex;

validator 配置

1
2
3
4
5
6
7
8
9
10
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

return validator;
}

本文地址: SpringBoot校验(validation)

本文地址: https://github.com/maxzhao-it/blog/post/33696/