Bean Validation | 8lovelife's life
0%

Bean Validation

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
2
3
4
5
6
7
8
9
10
11
Book book = new Book();
if (book != null
&& book.getCategory() != null
&& book.getCategory().trim().length() != 0
&& book.getTitle() != null
&& book.getTitle().trim().length() != 0
&& book.getWriter() != null
&& book.getWriter().trim().length() != 0) {
// TODO
}

验证参数 AFTER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Book {

@NotBlank
private String title;
@NotBlank
private String writer;
@NotBlank
private String category;

// setter/getter
}

validator.validate(new Book())

  • 减少频繁、冗余的if判断
  • Constrain once,validate everywhere

HOW

Bean Validation 怎么用?

  • Requirements(选择 Hibernate Validator实现)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    MAVEN依赖:

    <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
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
public class BeanForValidate {

/**
* 元素: CharSequence
* 约束:不能为null, 至少包含一个非空字符
*/
@NotBlank
private String blankStr = " ";


/**
* 元素:CharSequence(length)、Collection(size)、Map(size)、Array(length)
* 约束:不能为null, 不能为空
*/
@NotEmpty
private String emptyStr = " ";
@NotEmpty
private List<Integer> emptyList;


/**
* 元素:BigDecimal、BigInteger、byte、short、int、long(包括原型的包装类)
* 约束:min <= element <=max , null 合法
*/
@Min(value = 11)
@Max(value = 21)
// 11<=size<=21
private Integer minMaxInt = 10;
@Min(value = 21)
private BigDecimal minDecimal = new BigDecimal("20.9");


/**
* 元素:BigDecimal、BigInteger、CharSequence、byte、short、int、long(包括原型的包装类)
* 约束:element >= DecimalMin, null 合法
*/
@DecimalMin(value = "100.2")
private String decimalMinDecimal = "100.1";


/**
* 元素:CharSequence(length)、Collection(size)、Map(size)、Array(length)
* 约束:size(min) <= element >= size(max),null 合法
*/
@Size(min = 1, max = 3)
// 1<=size<=3
private String sizeStr = "size";
@Size(min = 1)
@NotNull
private List<String> nullSizeList = new ArrayList<>();

{
nullSizeList.add("A");
nullSizeList.add("B");
}


/**
* 元素:BigDecimal、BigInteger、byte、short、int、long、float、double(包括原型的包装类)
* 约束:必须是正数, 0非法 ,null 合法
*/
@Positive
private Integer positive = -1;


/**
* 元素:BigDecimal、BigInteger、CharSequence、byte、short、int、long(包括原型的包装类)
* 约束:element整数位数=integer , element小数位数=fraction ,null 合法
*/
@Digits(integer = 3, fraction = 2)
// 只允许在3位整数和2位小数范围内
private String digits = "99.212";


/**
* 元素:Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、
* OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate
* 约束:时间是在未来,当前时间默认的是 JVM的时间,null 合法
*/
@Future
private Date passTime;
{
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DAY_OF_YEAR, -1);
passTime = instance.getTime();
}


/**
* 元素:CharSequence
* 约束:是否符合正则表达式,null 合法
*/
@Pattern(message = "身份证账号格式错误", regexp = "(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)/")
// 身份证号验证
private String idCard = "92002199902137720";
@Email
@NotBlank
private String mail = "12.12@com.com";
}


校验:

BeanForValidate beanForValidate = new BeanForValidate();
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<BeanForValidate>> result = validator.validate(beanForValidate);
List<String> message
= result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
.collect(Collectors.toList());


结果:

positive must be greater than 0: -1
minMaxInt 最小不能小于11: 10
minDecimal 最小不能小于21: 20.9
decimalMinDecimal 必须大于或等于100.2: 100.1
idCard 身份证账号格式错误: 92002199902137720
sizeStr 个数必须在1和3之间: size
emptyList 不能为空: null
passTime 需要是一个将来的时间: Fri Apr 19 18:06:18 CST 2019
blankStr 不能为空:
digits 数字的值超出了允许范围(只允许在3位整数和2位小数范围内): 999.212

嵌套约束

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
public class BeanGraphForValidate {

@NotEmpty
private List<@Valid BeanForValidate> beanForValidates; // Bean Validation 2.0

// OR

// @NotEmpty
// @Valid 嵌套校验需要指定 @Valid注解
// private List<BeanForValidate> beanForValidatess;

@NotEmpty
private Map<String, @Valid BeanForValidate> beanForValidateMap; // Bean Validation 2.0

// OR

// @NotEmpty
// @Valid
// private Map<String,BeanForValidate> beanForValidateMapM;

{
beanForValidates = new ArrayList<>();
BeanForValidate beanForValidate = new BeanForValidate();
beanForValidates.add(beanForValidate);
beanForValidateMap = new HashMap<>();
beanForValidateMap.put("map", beanForValidate);
}

}


校验:

ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure().failFast(false).buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
BeanGraphForValidate beanGraphForValidate = new BeanGraphForValidate();
Set<ConstraintViolation<BeanGraphForValidate>> result = validator.validate(beanGraphForValidate);
List<String> message
= result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
.collect(Collectors.toList());

for (String str : message) {
System.out.println(str);
}


结果:

beanForValidateMap[map].emptyList 不能为空: null
beanForValidates[0].sizeStr validated size size: size
beanForValidateMap[map].blankStr 不能为空:
beanForValidateMap[map].property 不能为空: null
beanForValidateMap[map].passTime 需要是一个将来的时间: Mon Apr 22 00:44:45 CST 2019
beanForValidates[0].minDecimal the element is 20.9, but need element < 21: 20.9
beanForValidateMap[map].minMaxInt 最小不能小于11: 10
beanForValidates[0].decimalMinDecimal 必须大于或等于100.2: 100.1
beanForValidateMap[map].idCard 身份证账号格式错误: 92002199902137720
beanForValidates[0].positive must be greater than 0: -1
beanForValidateMap[map].digits 数字的值超出了允许范围(只允许在3位整数和2位小数范围内): 99.212
beanForValidates[0].minMaxInt 最小不能小于11: 10
beanForValidateMap[map].minDecimal the element is 20.9, but need element < 21: 20.9
beanForValidates[0].idCard 身份证账号格式错误: 92002199902137720
beanForValidates[0].property 不能为空: null
beanForValidates[0].passTime 需要是一个将来的时间: Mon Apr 22 00:44:45 CST 2019
beanForValidateMap[map].sizeStr validated size size: size
beanForValidates[0].blankStr 不能为空:
beanForValidates[0].digits 数字的值超出了允许范围(只允许在3位整数和2位小数范围内): 99.212
beanForValidateMap[map].positive must be greater than 0: -1
beanForValidateMap[map].decimalMinDecimal 必须大于或等于100.2: 100.1
beanForValidates[0].emptyList 不能为空: null

分组约束

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
public class BeanForGroupValidate {

interface ListGroup {
}

interface MapGroup {
}

@NotNull(groups = ListGroup.class, message = "listGroup") // 用于指定需要校验的组
private List<String> list;

@NotNull(groups = MapGroup.class, message = "mapGroup")
private Map<String, String> map;

@NotNull(message = "belong defaultGroup") // 未指明分组则为缺省组 Default.class
private Map<String, String> defaultMap;
}


校验:
validator.validate(beanGraphForValidate)
结果:
defaultMap belong to defaultGroup: null

校验:validator.validate(beanGraphForValidate, BeanForGroupValidate.ListGroup.class)
结果:
list listGroup: null

校验:
validator.validate(beanGraphForValidate, BeanForGroupValidate.MapGroup.class)
结果:
map mapGroup: null

方法参数约束

  • 入参
  • 出参
  • 构造器参数
  • 构造器返回
    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
    96
    public 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
    12
    MAVEN依赖:

    <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
2
3
4
5
6
7
8
9
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Bean
public Validator validator() {
ValidatorFactory vf = Validation.byProvider(HibernateValidator.class)
.configure().failFast(true)
.buildValidatorFactory();
return vf.getValidator();
}

Controller方法参数校验

  • Requirements
    Spring 容器中需要创建Bean:LocalValidatorFactoryBeanLocalValidatorFactoryBean包含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在解析参数的时候通过WebBindingInitializerOptionalValidatorFactoryBean绑定到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

Whiplash