在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数做校验
但是靠代码对接口参数一个个使用 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; |
| @Validated | Spring Validation验证框架提供,对@Valid的封装,符合Spring JSR-303规范,是标准JSR-303的一个变种 org.springframework.validation.annotation.Validated; |
@Valid与@Validated的区别
| 注解 | 区别 | 作用位置 |
|---|
| @Valid | 没有分组功能,提供嵌套校验的功能 | 可以用在方法、构造函数、方法参数和成员属性(字段)上 |
| @Validated | 提供了分组功能,没有嵌套校验的功能 | 可以在入参验证时,根据不同的分组采用不同的验证机制, 可以用在类型、方法和方法参数上。 不能用在成员属性(字段)上 |
空检查
| 验证注解 | 验证的数据类型 | 说明 |
|---|
| @Null | 任意类型 | 必须为null |
| @NotNull | 任意类型 | 不能为null,可以是空 |
| @NotBlank | CharSequence子类型 CharBuffer、String、 StringBuffer、StringBuilder | 只应用于字符串,且在比较时会去除字符串的首尾空格 字符串不能为null,trim()去除首尾空格后不能为空字符串” “ |
| @NotEmpty | CharSequence子类型、Collection、Map、数组 | 验证注解的元素值不为null且不为空,字符串长度不为0、集合大小不为0 |
Boolean检查
| 验证注解 | 验证的数据类型 | 说明 |
|---|
| @AssertFalse | Boolean、boolean | 验证注解的元素值是false |
| @AssertTrue | Boolean、boolean | 验证注解的元素值是true |
长度检查
| 验证注解 | 验证的数据类型 | 说明 |
|---|
| @Size(min=下限, max=上限) | 字符串、Collection、Map、数组等 | 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小 |
| @Length(min=下限, max=上限) | CharSequence子类型 | 验证注解的元素值长度在min和max区间内 |
日期检查
| 验证注解 | 验证的数据类型 | 说明 |
|---|
| @Past | java.util.Date,java.util.Calendar; Joda Time类库的日期类型 | 验证 Date 和 Calendar 对象是否在当前时间之前 |
| @Future | java.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=) | URL | ip地址校验,必须是一个URL |
| @CreditCardNumber | CharSequence的子类型 | 验证注解元素值是信用卡卡号 |
| @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
| <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
| <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> </dependency>
<dependency> <groupId>jakarta.validation</groupId> <artifactId>validation-api</artifactId> </dependency>
<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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.4</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <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;
AppHttpCodeEnum(int code, String errorMessage) { this.code = code; this.msg = errorMessage; }
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) public class ResponseResult<T> implements Serializable { private Integer code; private T data; 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; }
public Integer getCode() { return code; }
public void setCode(Integer code) { this.code = code; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; }
public T getData() { return data; }
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; }
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
|
@Slf4j
@RestControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(SystemException.class) public ResponseResult systemExceptionHandler(SystemException e) { log.error("出现了异常! {}", e); return ResponseResult.errorResult(e.getCode(), e.getMsg()); }
@ExceptionHandler(Exception.class) public ResponseResult exceptionHandler(Exception e) { log.error("出现了异常! {}", e); return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(), e.getMessage()); }
@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()); }
@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()); }
@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
| @Validated @RestController @RequestMapping("/user") public class TestController_1 {
@PostMapping("test1") public ResponseResult test1(@RequestBody @Valid UserDTO userDTO) { return ResponseResult.okResult(); }
@PostMapping("test2") public ResponseResult test2(@Valid UserDTO userDTO) { return ResponseResult.okResult(); }
@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
|
@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
|
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
|
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 {
@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 {
@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 4
| { "code": 400, "msg": "userName校验不通过,原因:名字为必填项" }
|
(6)启动程序,携带参数,向http://localhost:8080/user/addUser 发起post请求(模拟添加用户)
响应回的数据
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 {};
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;
@Override public void initialize(EnumString constraintAnnotation) { enumStringList = Arrays.asList(constraintAnnotation.value()); }
@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校验不通过,原因:性别只允许为男或女" }
|