WHAT
Bean Validation 是一个java规范。可以通过注解的方式约束定义的对象模型或约束方法的入参和出参对象
Bean Validation 1.0
Bean Validation 1.0(JSR303)是最早的一版java标准对象验证规范,是Java EE 6的一部分。认证的具体实现有:
名称 | 版本 |
---|---|
Hibernate Validator | 4.3.1.Final |
Apache BVal | 0.5 |
Bean Validation 1.1
Bean Validation 1.1(JSR349)是Java EE 7的一部分。
认证的具体实现有:
名称 | 版本 |
---|---|
Hibernate Validator | 4.3.1.Final |
Apache BVal | 1.1.2 |
较JSR303新增
- 方法级别上的约束,包括入参、出参
- DI and CDI(JSR346) 集成
- 支持组转换
- 违反约束消息支持EL表达式(JSR341)
- 集成其他规范,如验证 JAX-RS: Java API for RESTful Web Services
- more details
Bean Validation 2.0
Bean Validation 2.0(JSR380)是Java EE 8的一部分。
认证的具体实现有:
名称 | 版本 |
---|---|
Hibernate Validator | 6.0.1.Final |
较JSR380新增
- 可针对容器中的元素进行约束
- 支持date/time 的@Past、@Future约束
- 新的内置约束注解,如:@Email, @NotEmpty, @NotBlank, @Positive, @PositiveOrZero, @Negative, @NegativeOrZero, @PastOrPresent and @FutureOrPresent
- 可以通过反射取得字段值(field的注解约束不需要实现getter方法也可以进行约束校验,1.0和1.1必须实现getter方法)
- more details
WHY
为什么要使用Bean Validation ?
验证参数 BEFORE
1 | Book book = new Book(); |
验证参数 AFTER
1 | public class Book { |
- 减少频繁、冗余的if判断
- Constrain once,validate everywhere
HOW
Bean Validation 怎么用?
- Requirements(选择 Hibernate Validator实现)
1
2
3
4
5
6
7
8
9
10
11
12MAVEN依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b06</version>
</dependency>
约束
常用的注解约束解释
基本约束
1 | public class BeanForValidate { |
嵌套约束
1 | public class BeanGraphForValidate { |
分组约束
1 | public class BeanForGroupValidate { |
方法参数约束
- 入参
- 出参
- 构造器参数
- 构造器返回
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96public class MethodValidate {
@NotBlank
private String name;
@NotNull
private Integer age;
@NotNull
public MethodValidate(String name) {
this.name = name;
}
public MethodValidate(@NotNull Integer age) {
this.age = age;
}
@Size(min = 2, max = 2)
public List<String> getPeopleName() {
List<String> peoples = new ArrayList<>();
peoples.add(name);
return peoples;
}
public void setPeopleName(@NotBlank String name) {
this.name = name;
}
}
校验:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();
MethodValidate methodValidate = new MethodValidate("Mary");
Method method = MethodValidate.class.getMethod( "setPeopleName", String.class );
Object[] parameterValues = { "" };
Set<ConstraintViolation<MethodValidate>> validateSetPeopleName = executableValidator.validateParameters(
methodValidate,
method,
parameterValues
);
List<String> messages
= validateSetPeopleName.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
.collect(Collectors.toList());
System.out.println(messages);
method = MethodValidate.class.getMethod("getPeopleName");
List<String> peoples = new ArrayList<>();
peoples.add("Mary");
Set<ConstraintViolation<MethodValidate>> validateGetPeopleName = executableValidator.validateReturnValue(
methodValidate,
method,
peoples
);
messages
= validateGetPeopleName.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
.collect(Collectors.toList());
System.out.println(messages);
Constructor<MethodValidate> constructorParams = MethodValidate.class.getConstructor(Integer.class);
Object[] params = {null};
Set<ConstraintViolation<MethodValidate>> validateConstructor = executableValidator.validateConstructorParameters(
constructorParams,
params
);
messages
= validateConstructor.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
.collect(Collectors.toList());
System.out.println(messages);
Constructor<MethodValidate> constructorReturn = MethodValidate.class.getConstructor(String.class);
MethodValidate createdBean = new MethodValidate(18);
Set<ConstraintViolation<MethodValidate>> validateConstructorReturn = executableValidator.validateConstructorReturnValue(
constructorReturn,
createdBean
);
messages
= validateConstructorReturn.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
.collect(Collectors.toList());
System.out.println(messages);
结果:
[setPeopleName.arg0 不能为空: ]
[getPeopleName.<return value> 个数必须在2和2之间: [Mary]]
[MethodValidate.arg0 不能为null: null]
[]
集成
如何将Bean Validation集成到Spring MVC框架中?
- Requirements
1
2
3
4
5
6
7
8
9
10
11
12MAVEN依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b06</version>
</dependency>
扩展
1 | @Configuration |
Controller方法参数校验
Requirements
Spring 容器中需要创建Bean:LocalValidatorFactoryBean,LocalValidatorFactoryBean包含Bean Validation的实现类(通过SPI加载Bean Validation的实现,本文使用hibernate-validator实现)使用
1
2
3
4
5
6@RequestMapping(value = "/beanValidation", method = RequestMethod.POST)
public void testBeanValidation(@Valid BeanForValidate beanForValidate)
// 分组
@RequestMapping(value = "/beanValidation", method = RequestMethod.POST)
public void testBeanValidation(@Validated(value = Default.class) BeanForValidate beanForValidate)原理
SpringMVC在创建RequestMappingHandlerAdapter时会将OptionalValidatorFactoryBean设置到WebBindingInitializer属性中,RequestResponseBodyMethodProcessor在解析参数的时候通过WebBindingInitializer将OptionalValidatorFactoryBean绑定到WebDataBinder中,WebDataBinder通过判断参数是否含有@Valid或@Validated来决定是否进行参数校验
异常
MethodArgumentNotValidException
校验任意方法
Requirements
Spring 容器中需要创建Bean:MethodValidationPostProcessor使用
1
2
3
4
5
6
7
8
9@Validated
public interface BeanValidationService {
void testBeanValidation(@Valid BeanForValidate beanForValidate);
// 分组
@Validated
public interface BeanValidationService {
@Validated(value = Default.class)
void testBeanValidation(@Valid BeanForValidate beanForValidate);原理
MethodValidationPostProcessor会对含有@Validated的类或接口做AOP增强异常
ConstraintViolationException
@Valid VS @Validated
- @Valid
由validation-api定义,主要用于Bean的嵌套验证使用,也可用于判断Controller方法中的参数是否需要Bean Validation - Validated
由Spring定义,用于判断Controller方法中的参数是否需要Bean Validation以及用于开启Bean Validation的方法增强的标志。@Validated能够指定validation groups
If you’re too afraid to make a mistake, you’ll never play anything truly great. - Terence