Spring MVC 之 自定义参数校验(JSR 303 - Bean Validation)
系统提供对外接口或公用方法时,通常需要对入参校验,即直接在接口入口层就进行校验,不让非法参数流入到业务层导致异常,确保系统的健壮性。。
数据校验分客户端校验和服务端校验,客户端校验主要在页面通过JavaScript来实现,过滤正常用户的误操作,仅做初步过滤;服务端校验是整个应用阻止非法数据的最后防线,客户端校验绝不能替代服务端的校验,客户端校验可以降低服务器的负载。
Java EE 6 开始定义了一项为校验 Bean 数据合法性的规范 JSR-303,叫做 Bean Validation,该标准目标有两个实现:Hibernate Validator
和Apache bval
,使用较多的是前者。
Hibernate Validator (与 Hibernate ORM 没有关系)是 Bean Validation 的参考实现, 提供了 JSR-303 规范中所有内置 constraint
的实现,并扩展了一些常需要使用的 constraint,提供了一套比较完善,便捷的验证实现方式。
Validation
JSR-303
Bean Validation为 Java Bean 定义了相应的数据类型和 API,在应用中通过在Bean
属性上标注类似于@NotNull, @Max
等标准的注解指定校验规则,并通过验证接口对 Bean 进行验证。
该校验框架是一个运行时框架,在验证之后验证的错误信息被马上返回;核心接口是javax.validation.Validator
,该接口根据目标对象类中所标注的校验注解进和地数据校验,并得到校验结果。
内置校验注解
JSR-303(Bean Validation)内置的 constraint(约束):
Constraint | 描述 | 备注 |
---|---|---|
@Null | 被注释的元素必须为 null |
|
@NotNull | 被注释的元素必须不为 null |
通常作用在对象属性上, 作用在 String 类型上无法校验空格 |
@NotEmpty | 被注释的字符串的必须非空 | |
@NotBlank | 被注释的字符串必须不为 null ,且必须至少包含一个非空白字符 |
|
@AssertTrue | 被注释的元素必须为 true |
|
@AssertFalse | 被注释的元素必须为 false |
|
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 | |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 | |
@Past | 被注释的元素必须是一个过去的日期或时间 | |
@PastOrPresent | 被注释的元素必须是当前或一个过去的日期或时间 | |
@Future | 被注释的元素必须是一个将来的日期或时间 | |
@FutureOrPresent | 被注释的元素必须是当前或一个将来的日期或时间 | |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 | |
@Positive | 被注释的元素必须是一个严格的正数 | 0 为非法值 |
@PositiveOrZero | 被注释的元素必须是一个正数或为 0 | |
@Negative | 被注释的元素必须是一个严格的负数 | 0 为非法值 |
@NegativeOrZero | 被注释的元素必须是一个负数或为 0 | |
@Length | 被注释的字符串的大小必须在指定的范围内 | |
被注释的元素必须是电子邮箱地址 | ||
@Range | 被注释的元素必须在合适的范围内 |
Hibernate Validator
Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。更多信息,请查看:Hibernate Validator - The Bean Validation reference implementation.,Hibernate Validator 6.1.5.Final - Jakarta Bean Validation Reference Implementation: Reference Guide
扩展校验注解
Constraint | 描述 | 备注 |
---|---|---|
@Length | 被注释的字符串的长度必须在 min 与 max 之间 |
|
@URL | 被注释的字符串必须一个 URL | |
@Range | 被注释的元素必须在合适的范围内 | 应用于数值或数值的字符串表示形式 |
@CreditCardNumber | 被注释的字符串必须表示有效的信用卡号码 | 这是Luhn算法的实现,目的是检查用户的错误, 而不是信用卡的有效性! |
@LuhnCheck | Luhn 算法检查约束 | 允许验证一系列数字是否通过Luhn模10校验和算法, Luhn Mod10的计算方法是将数字相加, 每个奇数位(从右到左)的值乘以2,如果值大于9, 则结果数字a在总和之前求和。 |
@Mod10Check | 允许验证一系列数字是否通过Mod10校验和算法 | 经典的Mod10是通过将数字相加来计算的, 每一个奇数(从右到左)值乘以一个乘数 。 例如,ISBN-13是模10校验和(乘数是 3)。 |
@Mod11Check | 允许验证一系列数字是否通过Mod11校验和算法。 | 对于最常见的Mod11变量,求和是通过将最右边的数字(不包括校验位)乘以最左边的一个权重来完成的。 权重从2开始,每位数增加1。然后使用(sum%11)计算校验位。 |
@ISBN | 被注释的字符串必须是一个 ISBN 号 |
ISBN:指国际标准书号, 专为识别图书等文南而设计的国际编号 |
@UniqueElements | 验证所提供的 Collection中的每个对象是否唯一 | 即在集合中找不到2个相等的元素。 |
@ConstraintComposition | 布尔合成运算约束 | 值为 CompositionType 枚举(OR,AND,ALL_FALSE) |
已弃用 | ||
已弃用 | ||
已弃用 |
校验返回策略
Hibernate Validator 提供了两种校验策略模式:
- 普通模式:默认,会校验完所有的属性,然后返回所有的验证失败信息。
- 快速失败返回模式:只要有一个验证失败,就立即返回失败信息。
配置方式:addProperty
true 为快速失败返回模式,false 为普通模式。
1 |
|
手动使用 Validator 进行校验:
1 | // Lombok |
Spring Validator
Spring 提供了自己的数据校验框架。Spring 在进行数据绑定时,可同时调用校验框架来完成数据校验的工作。
Spring 的校验框架在org.springframework.validation
包中,重要的接口和类如下:
Validator:最重要的接口,里面有两个方法。
Errors:存放错误信息的接口。
校验的结果必须是Errors
或是BindingResult
类型。ValidatorUtils:校验的工具类,提供了多个
Errors
对象保存错误的方法。LocalValidatorFactoryBean:该类实现了Spring的
Validator
接口,也实现了JSR 303
的Validator
接口,只要在 Spring 容器中定义一个LocalValidatorFactoryBean
,即可将其注入到需要数据校验的Bean
中。1
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<mvc:annotation-driven/>
会默认装配好一个LocalValidatorFactoryBean,实现开发中不需要手动配置。
Spring MVC 提供的数据校验是通过硬编码完成的,实现开发推荐使用JSR 303
来进行数据校验。
Spring boot
如果是 Spring Boot 开发 Web 项目,spring-boot-starter-web
中已经含了 spring-boot-starter-validation
包,所以不需要重复添加依赖。
如果没有用 Web 依赖想要使用 Validator 实现 Bean 验证,则添加 spring-boot-starter-validation
依赖即可,该依赖内部引入的是 hibernate-validator
实现校验。
spring-boot-starter-validation 包的依赖关系:
1 | <dependencies> |
添加依赖
Spring Boot 项目添加 spring-boot-starter-validation 依赖,该依赖使用 Hibernate Validator
对 Java Bean 进行校验操作。
1 | <!--校验 --> |
Java Bean
1 | // Lombok |
开启校验
在 Java Bean 类上 或 属性上添加验证注解,还需要在 Spring MVC Controller 方法入参使用 @Validated
或 @Valid
注解开启校验。
@Valid:javax.validation 提供,作用在方法,构造方法,参数,成员属性上。
@Valid 可做嵌套校验,作用在属性上,属性是一个 Java Bean,需要对属性 Bean 的内部属性进行校验。
1
2
3
4
5
6
7
8
9
10
11
12
13// Lombok
public class Father {
private String name;
private List<Son> sonList;
}
public class Son {
private Integer age;
}@Validated:Spring 提供(扩展了 @Valid),作用在类,方法,参数上。
@Validated 支持分组,在接口形参上加上 @Validated({A.class}),只对该分组有效。例如,(@NotBlank(group={A.class},message = “name不能为空”)
注:A.class 可以是个空类,仅仅用于分组标识。
入参是 Java Bean
接口入参是 Java Bean 方式,@Valid 和 @Validated 都能起效。
1 |
|
入参非 Java Bean
此方式在参数前使用 @Valid 或 @Validated 都不能起效,只能使用 @Validated 并注释在此方法所在的 Controller 类上才能起效。
1 |
|
此方式验证抛出的异常为:ConstraintViolationException,可使用全局异常统一处理。见下面 异常统一处理 章节。
验证失败处理
返回验证失败时的错误信息(例如,通过统一异常处理捕获验证异常,取出验证的错误信息)。
绑定验证结果
在验证入参的方法中,可绑定入参校验的结果对象,判断结果对象是否存在错误信息。
1 |
|
异常统一处理
Spring Boot 使用 @ControllerAdvice
进行统一异常处理,Spring MVC 框架抛出方法参数校验异常:MethodArgumentNotValidException,取出异常中的错误信息返回给客户端。
全局异常统一处理参考:Spring Boot 2实践系列(三十八):全局异常统一处理
1 | if (e instanceof MethodArgumentNotValidException) { |
Spring 默认提供了一个配置校验器工厂 Bean,具体参考 SpringMVC-Validation数据校验。
自定义校验注解
框架提供的校验注解(constraint)可能并不能完全满足需求, 所以需要自定义校验规则。例如对 IP 地址进行校验,对密码复杂度进行校验等。
Java 的注解使用,可参考 Java基础:Java 注解(Annotation)及使用。
校验注解由两部分组成:
校验注解:就一个普通的 Java 注解,但多个了元注解
@Constraint
用于指定该注解的 校验器。见下面章节的示例。1
validatedBy 的值指定为校验逻辑的实现类,即校验器。
自定义校验注解必须包含 message,groups,payload 属性。
校验器:对校验注解进行解析,实现
ConstraintValidator
接口,接口使用了泛型,需要指定两个参数,第一个是自定义注解类,第二个是需要校验的数据类型,重写isValid(T value, ConstraintValidatorContext context)
方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public interface ConstraintValidator<A extends Annotation, T> {
/**
* 在调用 isValid 方法之前初始化
* 该方法的参数类型为自定义注解,可调用注解的方法获取注解信息
* 重写该方法, 给自定义校验器中定义的类变量赋值
*/
default void initialize(A constraintAnnotation) {
}
/**
* 重写该方法,实现校验逻辑,返回 布尔值
*/
boolean isValid(T value, ConstraintValidatorContext context);
}自定义注解校验器类实现了 ConstraintValidator 接口,默认会被 Spring 注册成 bean,所以可以在这个校验器类里面使用@Autowired 或者 @Resource 注入别的服务,不需要在类上添加 @Component 注解声明为 Spring 的 bean。
备注:自定义注解只能作用在接口 或 Controller 方法上才能启效,若写在实现类上是不启效的,只支持 Java Bean 验证。
自定义校验示例
金额精确2位小数
校验注解
1 | /** |
注解解析器
1 | /** |
保留小数位校验
校验注解
1 | /** |
注解解析器
1 | /** |
字符串是否在数组中
校验注解
1 | /** |
注解解析器
1 | import org.apache.commons.lang3.StringUtils; |
字符串是否在枚举中
校验注解
1 | /** |
注解解析器
1 | import org.apache.commons.lang3.StringUtils; |
数字是否在枚举中
校验注解
1 |
|
注解解析器
1 | public class IntegerEnumValidator implements ConstraintValidator<IntegerEnum, Integer> { |
注解使用
在实例类的属性上使用 @IntegerEnum 注解,指定枚举值。Controller 方法的入参增加 @Validated 注解使校验起效。
1 |
|
校验使用示例
pom.xml
文件引入Hibernate Validator jar包
和依赖包。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!-- JSR 303数据校验实现:hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.7.Final</version>
</dependency>
<!-- javax.el -->
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.5</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.5</version>
</dependency>实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class People {
private String name;
private String loginName;
private String password;
private String userName;
private double age;
private String email;
private Date birthDay;
private String phone;
//-------set/get方法------------
}Controller代码
方法中接收参数的对象添加@Valid
注解1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LoginController {
public String registerForm() {
return "registerForm";
}
public String login( People people, Errors errors, Model model){
System.err.println(people);
System.err.println(errors);
if(errors.hasErrors()) {
String defaultMessage = errors.getFieldError("age").getDefaultMessage();
System.out.println(defaultMessage);
return "registerForm";
}
model.addAttribute("people", people);
return "success";
}
}JSP表单代码
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<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript"
src="${pageContext.request.contextPath}/static/js/jquery-1.11.0.js"></script>
<title>JSR 303 Validation</title>
</head>
<body>
<h3>注册页面</h3>
<form action="${pageContext.request.contextPath}/user/login"
method="post">
<table>
<tr>
<td>登录名:</td>
<td><input name="loginName"></td>
</tr>
<tr>
<td>密码:</td>
<td><input name="password"></td>
</tr>
<tr>
<td>用户名:</td>
<td><input name="userName"></td>
</tr>
<tr>
<td>年龄:</td>
<td><input name="age"></td>
</tr>
<tr>
<td>邮箱:</td>
<td><input name="email"></td>
</tr>
<tr>
<td>生日:</td>
<td><input name="birthDay"></td>
</tr>
<tr>
<td>电话:</td>
<td><input name="phone"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form>
</body>
</html>
相关参考
- JSR 303 - Bean Validation 介绍及最佳实践
- Hibernate Validator:Bean Validation 的参考实现
- Hibernate Validator 6.1.5.Final - Jakarta Bean Validation Reference Implementation: Reference Guide
- JSR 303: Bean Validation
- JSR 380: Bean Validation 2.0
- Jakarta Bean Validation
- Jakarta-bean-validation 校验示例
- Validator 自动化校验
- Spring/Spring boot JSR-303验证框架 之 hibernate-validator
- Hibernate Validator 使用介绍
- 参数校验工具之Validator自定义校验
Spring MVC 之 自定义参数校验(JSR 303 - Bean Validation)
http://blog.gxitsky.com/2020/07/02/SpringMVC-37-JSR-303-BeanValidation-Hibernate-Validator/