在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数做校验

但是靠代码对接口参数一个个使用 if-else 校验的话就太繁琐了,代码可读性极差

Validator框架专门用来进行接口参数的校验,使开发人员在开发的时候少写代码,提升了开发效率;

JSR规范

JCP与JSR

(1)JCP代表Java社区流程(Java Community Process),是由Oracle公司创建的一个开放组织,提供了一套明确的流程和机制,使任何人都能够参与对Java技术的发展和规范制定过程的审阅和反馈,旨在管理和推动Java技术和平台的发展

(2)JSR代表Java规范请求(Java Specification Request),当有人提出一项新的Java规范时,他们将向JCP组织提交一个JSR,一旦JSR被接受,就会组建一个专门的工作组来开发和推动该规范的制定过程。

现有的JSR规范

Web Service技术

  • Java Date与Time API (JSR 310)
  • Java API for RESTful Web Services (JAX-RS) 1.1 (JSR 311)
  • Implementing Enterprise Web Services 1.3 (JSR 109)
  • Java API for XML-Based Web Services (JAX-WS) 2.2 (JSR 224)
  • Java Architecture for XML Binding (JAXB) 2.2 (JSR 222)
  • Web Services Metadata for the Java Platform (JSR 181)
  • Java API for XML-Based RPC (JAX-RPC) 1.1 (JSR 101)
  • Java APIs for XML Messaging 1.3 (JSR 67)
  • Java API for XML Registries (JAXR) 1.0 (JSR 93)

Web应用技术

  • Java Servlet 3.0 (JSR 315)
  • JavaServer Faces 2.0 (JSR 314)
  • JavaServer Pages 2.2/Expression Language 2.2 (JSR 245)
  • Standard Tag Library for JavaServer Pages (JSTL) 1.2 (JSR 52)
  • Debugging Support for Other Languages 1.0 (JSR 45)

企业应用技术

  • Contexts and Dependency Injection for Java (Web Beans 1.0) (JSR 299)
  • Dependency Injection for Java 1.0 (JSR 330)@postConstruct, @PreDestroy
  • Bean Validation 1.0 (JSR 303)
  • Enterprise JavaBeans 3.1 (includes Interceptors 1.1) (JSR 318)
  • Java EE Connector Architecture 1.6 (JSR 322)
  • Java Persistence 2.0 (JSR 317)
  • Common Annotations for the Java Platform 1.1 (JSR 250)
  • Java Message Service API 1.1 (JSR 914)
  • Java Transaction API (JTA) 1.1 (JSR 907)
  • JavaMail 1.4 (JSR 919)

管理与安全技术

  • Java Authentication Service Provider Interface for Containers (JSR 196)
  • Java Authorization Contract for Containers 1.3 (JSR 115)
  • Java EE Application Deployment 1.2 (JSR 88)
  • J2EE Management 1.1 (JSR 77)

Java SE中与Java EE有关的规范

  • JCache API (JSR 107)
  • Java Memory Model (JSR 133)
  • Concurrency Utilitie (JSR 166)
  • Java API for XML Processing (JAXP) 1.3 (JSR 206)
  • Java Database Connectivity 4.0 (JSR 221)
  • Java Management Extensions (JMX) 2.0 (JSR 255)
  • Java Portlet API (JSR 286)
  • 模块化 (JSR 294)
  • Swing应用框架 (JSR 296)
  • JavaBeans Activation Framework (JAF) 1.1 (JSR 925)
  • Streaming API for XML (StAX) 1.0 (JSR 173)

JSR-303规范(Bean Validation)

JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,使用注解方式实现,及其方便,但是这只是一个接口,没有具体实现

Hibernate Validator简介

Hibernate Validator 对 Bean Validation 规范进行了实现,同时做出了扩展,是一个独立的包,可以直接引用,不仅提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的约束 constraint。通常会使用Hibernate Validation对参数校验

Spring Validation验证框架

Spring Validation是基于Hibernate Validation的实现的二次封装,关系如下图

常用的校验约束注解

@Valid注解与@Validated注解

注解简介
@Valid由Jdk提供,配合BindingResult可以直接提供参数验证结果,符合标准JSR-303规范 javax.validation.Valid;
@ValidatedSpring Validation验证框架提供,对@Valid的封装,符合Spring JSR-303规范,是标准JSR-303的一个变种 org.springframework.validation.annotation.Validated;

@Valid与@Validated的区别

注解区别作用位置
@Valid没有分组功能,提供嵌套校验的功能可以用在方法、构造函数、方法参数和成员属性(字段)上
@Validated提供了分组功能,没有嵌套校验的功能可以在入参验证时,根据不同的分组采用不同的验证机制, 可以用在类型、方法和方法参数上。 不能用在成员属性(字段)上

空检查

验证注解验证的数据类型说明
@Null任意类型必须为null
@NotNull任意类型不能为null,可以是空
@NotBlankCharSequence子类型 CharBuffer、String、 StringBuffer、StringBuilder只应用于字符串,且在比较时会去除字符串的首尾空格 字符串不能为null,trim()去除首尾空格后不能为空字符串” “
@NotEmptyCharSequence子类型、Collection、Map、数组验证注解的元素值不为null且不为空,字符串长度不为0、集合大小不为0

Boolean检查

验证注解验证的数据类型说明
@AssertFalseBoolean、boolean验证注解的元素值是false
@AssertTrueBoolean、boolean验证注解的元素值是true

长度检查

验证注解验证的数据类型说明
@Size(min=下限, max=上限)字符串、Collection、Map、数组等验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小
@Length(min=下限, max=上限)CharSequence子类型验证注解的元素值长度在min和max区间内

日期检查

验证注解验证的数据类型说明
@Pastjava.util.Date,java.util.Calendar; Joda Time类库的日期类型验证 Date 和 Calendar 对象是否在当前时间之前
@Futurejava.util.Date,java.util.Calendar; Joda Time类库的日期类型验证 Date 和 Calendar 对象是否在当前时间之后

数值检查

验证注解验证的数据类型说明
@MIN(value=值)任何Number或CharSequence(存储的是数字)子类型 BigDecimal、BigInteger、byte、short、int、long等验证注解的元素值大于等于@Min指定的value值
@MAX(value=值)和@Min要求一样验证注解的元素值小于等于@Max指定的value值
@DecimalMin(value=值)和@Min要求一样验证注解的元素值大于等于@ DecimalMin指定的value值
@DecimalMax(value=值)和@Min要求一样验证注解的元素值小于等于@ DecimalMax指定的value值
@Digits(integer=整数位数, fraction=小数位数)和@Min要求一样验证字符串是否是符合指定格式的数字, interger指定整数精度, fraction指定小数精度
@Range(min=最小值, max=最大值)BigDecimal、BigInteger, CharSequence、byte、short int、long等原子类型和包装类型验证注解的元素值在最小值和最大值之间

其他检查

验证注解验证的数据类型说明
@Valid任何非原子类型指定递归验证关联的对象; 如用户对象中有个地址对象属性, 如果想在验证用户对象时一起验证地址对象的话, 在地址对象上加@Valid注解即可级联验证
@Pattern(regexp=正则表达式,flag=标志的模式)String,CharSequence的子类型验证 String 对象是否符合正则表达式的规则
@Email(regexp=正则表达式,flag=标志的模式)CharSequence的子类型验证是否是邮件地址,如果为null,不进行验证,算通过验证, 也可以通过regexp和flag指定自定义的email格式
@URL(protocol=,host=, port=,regexp=, flags=)URLip地址校验,必须是一个URL
@CreditCardNumberCharSequence的子类型验证注解元素值是信用卡卡号
@ScriptAssert(lang= ,script=)业务类校验复杂的业务逻辑

Validation校验注解作用域

注解作用域
@Validated、@Valid校验实体entity类型
@NotBlank校验String类型
@NotNull校验基本类型
@NotEmpty校验集合类型

使用案例

依赖引入方式

(1)Springboot-2.3之前的版本只需要引入 web 依赖就可以,从Springboot-2.3开始,校验包被独立成了一个starter组件,所以需要引入Validation和web

1
2
3
4
5
<!-- 引入validation校验依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

(2)如果不是 Springboot 项目,那么引入下面依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--引入Bean Validation(javax规范的版本)-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>

<!--引入Bean Validation(jakarta规范的版本)-->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>

<!--引入Hibernate Validation校验依赖-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>

案例准备工作

(1)创建Maven项目,在pom文件引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--parent标签类似java中的继承,复用依赖,减少冗余配置-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
</parent>
<dependencies>
<!--SpringMVC对应的starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入validation校验依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- lombk依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

(2)需要进行参数校验的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
public class UserDTO {
@NotNull(message = "id为必填项")
private Long id;

@NotBlank(message = "名字为必填项")
private String userName;

@Length(min = 6, max = 12, message = "密码长度必须位于6到12之间")
private String password;

@Range(min = 0, max = 120, message = "年龄应在0-120岁")
private Integer age;

@Email(message = "请填写正确的邮箱地址")
private String email;
}

(3)启动类

1
2
3
4
5
6
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}

统一响应格式

(1)自定义枚举类:封装 响应码 与 响应信息

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
/**
* 自定义枚举类:封装 响应码 与 响应信息
*/
public enum AppHttpCodeEnum {
// 枚举类对象必须在第一行声明,多个枚举类对象之间用","隔开,用";"结束
SUCCESS(200, "操作成功"),
NEED_LOGIN(401, "需要登录后操作"),
NO_OPERATOR_AUTH(403, "无权限操作"),
SYSTEM_ERROR(500, "出现错误"),
UNAUTHORIZED(401,"未认证"),
NOT_FOUND(404,"找不到资源"),
ACCOUNT_EXIST(501, "账号已存在"),
PHONENUMBER_EXIST(501, "手机号已存在"),
EMAIL_EXIST(503, "邮箱已存在"),
REQUIRE_USERNAME(504, "必需填写账号"),
LOGIN_ERROR(505, "用户名或密码错误"),
CONTENT_NOT_NULL(506, "评论内容不能为空"),
FILE_TYPE_ERROR(507, "文件类型错误,仅支持png、jpg、jpeg、gif、bmp"),
USERACCOUNT_NOT_NULL(508, "用户账号不能为空"),
PASSWORD_NOT_NULL(509, "用户密码不能为空"),
EMAIL_NOT_NULL(510, "用户邮箱不能为空"),
NICKNAME_NOT_NULL(510, "用户昵称不能为空");
int code;// 状态码
String msg;// 响应信息

// 私有化的构造器,给对象属性初始化(枚举类的构造器只能使用 private 权限修饰符)
AppHttpCodeEnum(int code, String errorMessage) {
this.code = code;
this.msg = errorMessage;
}

// 只需要添加get方法就行
public int getCode() {
return code;
}

public String getMsg() {
return msg;
}
}

(2)统一响应格式Result类

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
@JsonInclude(JsonInclude.Include.NON_NULL)// @JsonInclude注解:为null的字段不序列化
public class ResponseResult<T> implements Serializable {
// 封装操作结果到flag中(状态码)
private Integer code;
// 封装数据到data属性中(数据内容)
private T data;
// 封装特殊消息到message 属性msg中(状态信息)
private String msg;

// 构造器
public ResponseResult() {
this.code = AppHttpCodeEnum.SUCCESS.getCode();
this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
}

// 构造器
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}

// 构造器
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}

// 构造器
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

// 响应成功方法:返回空参
public static ResponseResult okResult() {
ResponseResult result = new ResponseResult();
return result;
}

// 响应成功方法:返回状态码、状态信息(状态码、状态信息来自枚举)
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}

// 响应成功方法:返回数据内容、状态码、状态信息(状态码、状态信息来自枚举)
public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
if (data != null) {
result.setData(data);
}
return result;
}

// 响应失败方法:返回状态码、状态信息(状态码、状态信息来自枚举)
public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}

// 响应失败方法:返回状态码、状态信息(状态码、状态信息来自枚举)
public static ResponseResult errorResult(AppHttpCodeEnum enums) {
return setAppHttpCodeEnum(enums, enums.getMsg());
}

// 响应失败方法:返回状态码、状态信息(状态码、状态信息来自枚举)
public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg) {
return setAppHttpCodeEnum(enums, msg);
}

public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums) {
return okResult(enums.getCode(), enums.getMsg());
}

private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg) {
return okResult(enums.getCode(), msg);
}

// 响应失败方法:返回状态码、状态信息(状态码、状态信息来自枚举)
public ResponseResult<?> error(Integer code, String msg) {
this.code = code;
this.msg = msg;
return this;
}

// 响应成功方法:返回数据内容、状态码(状态码来自枚举)
public ResponseResult<?> ok(Integer code, T data) {
this.code = code;
this.data = data;
return this;
}

// 响应成功方法:返回数据内容、状态码、状态信息(状态码、状态信息来自枚举)
public ResponseResult<?> ok(Integer code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
return this;
}

// 响应成功方法:返回数据内容
public ResponseResult<?> ok(T data) {
this.data = data;
return this;
}

// get方法:获取状态码
public Integer getCode() {
return code;
}

// set方法:设置状态码
public void setCode(Integer code) {
this.code = code;
}

// get方法:获取状态信息
public String getMsg() {
return msg;
}

// set方法:设置状态信息
public void setMsg(String msg) {
this.msg = msg;
}

// get方法:获取数据内容
public T getData() {
return data;
}

// set方法:设置数据内容
public void setData(T data) {
this.data = data;
}

}

全局异常处理

Validation参数校验三种异常情况

异常情况简介
org.springframework.web.bind.MethodArgumentNotValidException作用于 @Validated @Valid 注解,前端提交的方式为json格式有效,出现异常时会被该异常类处理
org.springframework.validation.BindException作用于 @Validated @Valid 注解,仅对于表单提交有效,对于以json格式提交将会失效
javax.validation.ConstraintViolationException作用于 @NotBlank @NotNull @NotEmpty 注解,校验单个String、Integer、Collection等参数异常处理。

自定义异常处理类

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 SystemException extends RuntimeException {

private int code;

private String msg;

public int getCode() {
return code;
}

public String getMsg() {
return msg;
}

/**
* 构造方法
*
* @param httpCodeEnum AppHttpCodeEnum为自定义枚举类,封装 状态码 与 返回信息
*/
public SystemException(AppHttpCodeEnum httpCodeEnum) {
super(httpCodeEnum.getMsg());
this.code = httpCodeEnum.getCode();
this.msg = httpCodeEnum.getMsg();
}

public SystemException(int code, String msg) {
this.code = code;
this.msg = msg;
}
}

全局异常处理类

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
/**
* 全局异常处理,出现的异常只要在其中声明即可捕获
*/
// 由于自己开启日志需要写上:private final Logger logger = LoggerFactory.getLogger(XXX.class);
// 为了方便起见就可以使用注解@Slf4j来直接使用log对象,简化了一行代码(lombok提供)
@Slf4j
// @ControllerAdvice注解:将类标识为异常处理组件
// @ResponseBody注解:将java对象直接转换为json字符串并响应到浏览器
// @RestControllerAdvice = @ControllerAdvice + @ResponseBody
@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* 全局处理SystemException异常
*
* @param e 自定义的SystemException异常
* @return Json格式响应数据,包括响应码code、响应提示数据msg
*/
// @ExceptionHandler注解:说明捕获哪些异常,对哪些异常进行处理。
@ExceptionHandler(SystemException.class)
public ResponseResult systemExceptionHandler(SystemException e) {
// 打印异常信息
log.error("出现了异常! {}", e);
// 从异常对象中获取提示信息封装返回
return ResponseResult.errorResult(e.getCode(), e.getMsg());
}

/**
* 全局处理Exception异常
*
* @param e 程序可以处理的异常,运行时异常和编译时异常
* @return Json格式响应数据,包括响应码code、响应提示数据msg
*/
// @ExceptionHandler注解:说明捕获哪些异常,对哪些异常进行处理。
@ExceptionHandler(Exception.class)
public ResponseResult exceptionHandler(Exception e) {
// 打印异常信息
log.error("出现了异常! {}", e);
// 从异常对象中获取提示信息封装返回
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(), e.getMessage());
}


/**
* 全局处理MethodArgumentNotValidException异常,json格式才会有这种异常
*
* @param e Spring封装的参数验证异常处理,作用于 @Validated @Valid 注解,接收参数加上@RequestBody注解
* @return Json格式响应数据,包括响应码code、响应提示数据msg
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 打印异常信息
log.error("出现了异常! {}", e);
List<FieldError> fieldErrors = e.getFieldErrors();
StringBuilder stringBuilder = new StringBuilder();
for (FieldError f : fieldErrors) {
System.out.println("出错的属性:" + f.getField());
System.out.println("自定义的提示信息:" + f.getDefaultMessage());
stringBuilder.append(f.getField() + "校验不通过," + "原因:" + f.getDefaultMessage());
}
return ResponseResult.errorResult(400, stringBuilder.toString());
}

/**
* 全局处理BindException异常,仅对于表单提交有效,对于以json格式提交将会失效
*
* @param e 参数校验异常,作用于@Validated@Valid 注解
* @return Json格式响应数据,包括响应码code、响应提示数据msg
*/
// @ExceptionHandler注解:说明捕获哪些异常,对哪些异常进行处理。
@ExceptionHandler(BindException.class)
public ResponseResult bindExceptionHandler(BindException e) {
// 打印异常信息
log.error("出现了异常! {}", e);
List<FieldError> fieldErrors = e.getFieldErrors();

StringBuilder stringBuilder = new StringBuilder();
for (FieldError f : fieldErrors) {
System.out.println("出错的属性:" + f.getField());
System.out.println("自定义的提示信息:" + f.getDefaultMessage());
stringBuilder.append(f.getField() + "校验不通过," + "原因:" + f.getDefaultMessage());
}
return ResponseResult.errorResult(400, stringBuilder.toString());
}

/**
* 全局处理ConstraintViolationException异常信息,校验单个String、Integer、Collection等参数异常处理
*
* @param e jsr规范中的验证异常,嵌套检验问题,作用于 @NotBlank @NotNull @NotEmpty 注解,Controller类上必须添加@Validated注解,否则接口单个参数校验无效
* @return Json格式响应数据,包括响应码code、响应提示数据msg
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseResult constraintViolationExceptionHandler(ConstraintViolationException e) {
// 打印异常信息
log.error("出现了异常! {}", e);
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
StringBuilder stringBuilder = new StringBuilder();
for (ConstraintViolation<?> c : constraintViolations) {
c.getPropertyPath();
System.out.println("出错的属性:" + c.getPropertyPath());
System.out.println("自定义的提示信息:" + c.getMessage());
stringBuilder.append(c.getPropertyPath() + "校验不通过," + "原因:" + c.getMessage());
}
return ResponseResult.errorResult(400, stringBuilder.toString());
}

}

第一种校验方式(适用于生产)

(1)控制层使用

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
// 当使用单参数校验时需要在Controller上加上@Validated注解,否则不生效
@Validated
@RestController
@RequestMapping("/user")
public class TestController_1 {

/**
* 使用了@RequestBody注解,用于接受前端发送的json数据
*/
@PostMapping("test1")
public ResponseResult test1(@RequestBody @Valid UserDTO userDTO) {
return ResponseResult.okResult();
}

/**
* 不使用@RequestBody注解,模拟表单提交
*/
@PostMapping("test2")
public ResponseResult test2(@Valid UserDTO userDTO) {
return ResponseResult.okResult();
}

/**
* 模拟单参数提交
* 使用单参数校验时需要在Controller上加上@Validated注解,否则不生效
*/
@PostMapping("test3")
public ResponseResult test3(@Email String email) {
return ResponseResult.okResult();
}

}

(2)启动程序,携带参数,向http://localhost:8080/user/test1 发起post请求

1
2
3
4
5
6
7
{
"id":1,
"userName":"小明",
"password":123456,
"age":18,
"email":"123"
}

响应回的数据

1
2
3
4
{
"code": 400,
"msg": "email校验不通过,原因:请填写正确的邮箱地址"
}

(3)启动程序,携带参数,向http://localhost:8080/user/test2 发起post请求

响应回的数据

1
2
3
4
{
"code": 400,
"msg": "email校验不通过,原因:请填写正确的邮箱地址"
}

(4)启动程序,携带参数,向http://localhost:8080/user/test3发起post请求

响应回的数据

1
2
3
4
{
"code": 400,
"msg": "原因:不是一个合法的电子邮件地址"
}

第二种校验方式

(1)在Controller方法参数前加@Valid注解,参数后面定义一个BindingResult类型参数,执行时会将校验结果放进bindingResult里面,用户自行判断并处理

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
/**
* 在Controller方法参数前加@Valid注解,
* 参数后面定义一个BindingResult类型参数
* 执行时会将校验结果放进bindingResult里面,用户自行判断并处理
*/
@RestController
@RequestMapping("/user")
public class TestController_2 {

@PostMapping("test4")
public ResponseResult test(@RequestBody @Valid UserDTO userDTO, BindingResult bindingResult) {
// 参数校验
if (bindingResult.hasErrors()) {
List<ObjectError> allErrors = bindingResult.getAllErrors();
StringBuilder stringBuilder = new StringBuilder();
for (ObjectError allError : allErrors) {
System.out.println("出错的属性:" + allError.getCode());
System.out.println("自定义的提示信息:" + allError.getDefaultMessage());
stringBuilder.append(allError.getCode() + "校验不通过,").append("原因:" + allError.getDefaultMessage());
}
//这里可以抛出自定义异常,或者进行其他操作
return ResponseResult.errorResult(400, stringBuilder.toString());
}
return ResponseResult.okResult();
}
}

(2)启动程序,携带参数,向http://localhost:8080/user/test4 发起post请求

1
2
3
4
5
6
7
{
"id":1,
"userName":"小明",
"password":123456,
"age":18,
"email":"123"
}

(3)响应回的数据

1
2
3
4
{
"code": 400,
"msg": "email校验不通过,原因:请填写正确的邮箱地址"
}

第三种校验方式

用户手动调用对应API执行校验,这种方法适用于校验任意一个有valid注解的实体类,并不仅仅是只能校验接口中的参数;

Validation.buildDefault ValidatorFactory().getValidator().validate(xxx)

(1)创建校验参数工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 手动调用api方法校验对象
*/
public class MyValidationUtils {
public static void validate(@Valid Object value) {
Set<ConstraintViolation<@Valid Object>> validateSet = Validation.buildDefaultValidatorFactory()
.getValidator()
.validate(value);
StringBuilder stringBuilder = new StringBuilder();
if (!CollectionUtils.isEmpty(validateSet)) {
for (ConstraintViolation<Object> v : validateSet) {
System.out.println("出错的属性:" + v.getPropertyPath());
System.out.println("自定义的提示信息:" + v.getMessage());
stringBuilder.append(v.getPropertyPath() + "校验不通过," + "原因:" + v.getMessage());
}
//这里可以抛出自定义异常,或者进行其他操作
throw new SystemException(400, stringBuilder.toString());
}
}
}

(2)使用自定义的参数校验工具类校验参数

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/user")
public class TestController_3 {

@PostMapping("test5")
public ResponseResult test(@RequestBody @Valid UserDTO userDTO) {
// 调用自定义的参数校验工具类校验参数
MyValidationUtils.validate(userDTO);
return ResponseResult.okResult();
}

}

分组校验

一个VO对象的某些字段在新增时必填,在更新时又非必填,基于这种场景Validator校验框架提供了分组校验

(1)定义分组接口ValidGroup继承javax.validation.groups.Default,在分组接口中定义出多个不同的操作类型,create,delete,update,select。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 分组接口,继承javax.validation.groups.Default,接口中定义不同的操作类型分组
*/
public interface ValidGroup extends Default {
interface Crud extends ValidGroup {
// 添加组
interface create extends Crud {
}

//删除组
interface Delete extends Crud {
}

// 更新组
interface update extends Crud {
}

// 查询组
interface select extends Crud {
}

}
}

(2)在实体类中给参数分配分组,未指定分组使用的是默认分组jakarta.validation.groups.Default

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
public class UserDTO {
/**
* 创建时id可以为空,更新时id不能为空
*/
@Null(groups = ValidGroup.Crud.create.class, message = "ID必须为空")
@NotNull(groups = ValidGroup.Crud.update.class, message = "ID不能为空")
private Long id;

/**
* 创建时名称必须填写
*/
@NotBlank(groups = ValidGroup.Crud.create.class, message = "名字为必填项")
private String userName;
}

(3)给需要参数校验的方法指定分组(通过@Validated注解的value属性指定分组)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/user")
public class TestController {
/**
* 通过@Validated注解的value属性指定分组
*/
@PostMapping("/addUser")
public ResponseResult addUser(@Validated(value = ValidGroup.Crud.create.class) @RequestBody UserDTO userDTO) {
return ResponseResult.okResult();
}

@PostMapping("/updateUser")
public ResponseResult updateUser(@Validated(value = ValidGroup.Crud.update.class) @RequestBody UserDTO userDTO) {
return ResponseResult.okResult();
}
}

(4)启动程序,携带参数,向http://localhost:8080/user/addUser 发起post请求(模拟添加用户)

1
2
3
4
{
"id":1,
"userName":"小明",
}

响应回的数据

1
2
3
4
{
"code": 400,
"msg": "id校验不通过,原因:ID必须为空"
}

(5)启动程序,携带参数,向http://localhost:8080/user/addUser 发起post请求(模拟添加用户)

1
2
3
{
"userName":null,
}

响应回的数据

1
2
3
4
{
"code": 400,
"msg": "userName校验不通过,原因:名字为必填项"
}

(6)启动程序,携带参数,向http://localhost:8080/user/addUser 发起post请求(模拟添加用户)

1
2
3
{
"userName":"小明",
}

响应回的数据

1
2
3
4
{
"code": 200,
"msg": "操作成功"
}

自定义参数校验

虽然Spring Validation 提供的注解基本上够用,但是面对复杂的定义,还是需要自己定义相关注解来实现自动校验。例如sex性别属性,如果只允许前端传递1或2,可以根据Validator框架定义好的注解来仿写,来自定义校验逻辑

(1)自定义注解@EnumString

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
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumString.List.class)
@Documented
// 标明由哪个类执行校验逻辑
@Constraint(validatedBy = EnumStringValidator.class)
public @interface EnumString {
String message() default "value not in enum values.";

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

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

/**
* @return Date必须在此值数组中
*/
String[] value();


@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interface List {
EnumString[] value();
}
}

(2)自定义校验逻辑

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
public class EnumStringValidator implements ConstraintValidator<EnumString, String> {
private List<String> enumStringList;

/**
* initialize()会在校验实例化后被调用,一般用于做些初始化工作
*/
@Override
public void initialize(EnumString constraintAnnotation) {
enumStringList = Arrays.asList(constraintAnnotation.value());
}

/**
* isValid()中写自己的逻辑,是实际执行验证的方法
* @param value 字段或对象实际对应的值,取决于类的枚举限定类型
* @param context 上下文可以做些默认的设置
* @return 返回true为校验通过,返回false为校验失败,给出错误信息message。
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return enumStringList.contains(value);
}
}

(3)在实体类字段上使用自定义注解

1
2
3
4
5
6
7
8
9
@Data
@ToString
public class UserDTO {
@NotNull(message = "id为必填项")
private Long id;

@EnumString(value = {"男", "女"}, message = "性别只允许为男或女")
private String sex;
}

(4)控制层

1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/user")
public class TestController {
@PostMapping("/test")
public ResponseResult test(@RequestBody @Valid UserDTO userDTO) {
return ResponseResult.okResult();
}
}

(5)启动程序,携带参数,向http://localhost:8080/user/test 发起post请求

1
2
3
4
{
"id":1,
"sex":"test"
}

(6)响应回的数据

1
2
3
4
{
"code": 400,
"msg": "sex校验不通过,原因:性别只允许为男或女"
}