在web项目开发过程中,参数的校验是十分重要的。入参少的情况下使用if else即可处理,但是入参多而的情况下if else就显得力不从心了。本章介绍validator注解的方式进行参数校验。
一、什么是Validator
Bean Validation是Java定义的一套基于注解的数据校验规范。在SpringBoot中已经集成在 starter-web中,所以无需在添加其他依赖。
org.hibernate.validator
hibernate-validator
6.0.18.Final
compile
二、注解
2.1 Bean Validation 中内置的 constraint
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式
注意:
- @NotNull 适用于任何类型被注解的元素必须不能与NULL
- @NotEmpty 适用于String Map或者数组不能为Null且长度必须大于0
- @NotBlank 只能用于String上面 不能为null,调用trim()后,长度必须大于0
2.2 Hibernate Validator 附加的 constraint
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内
三、入参校验
这里模拟新增用户
3.1 入参校验规则
在入参实体上使用注解定义校验规则
package pers.simon.boot.validator.po;
import lombok.Data;
import pers.simon.boot.validator.annotate.IdCard;
import javax.validation.constraints.*;
import java.util.Date;
/**
* @author simon
* @date 2019/12/10 10:28
*/
@Data
public class UserVo {
@Null(message = "用户ID必须为空")
private String userId;
@NotBlank(message = "用户名不能为空")
private String userName;
@NotNull(message = "生日不能为空")
@Past(message = "生日必须为过去日期")
private Date birthday;
@NotNull(message = "年龄不能为空")
@Min(value = 10,message = "年龄必须大于10")
@Max(value = 30,message = "年龄必须小于30")
private Integer age;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式错误")
private String email;
@NotNull(message = "手机号不能为空")
@NotBlank(message = "手机号不能为空")
@Pattern(regexp ="^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误")
@Max(value = 11,message = "手机号只能为11位")
@Min(value = 11,message = "手机号只能为11位")
private String phone;
}
3.2 在controller方法体添加@Validated
controller方法体不加@Validated校验会不起作用
package pers.simon.boot.validator.controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import pers.simon.boot.validator.po.UserVo;
import pers.simon.boot.validator.utils.JsonResult;
/**
*
* @author simon
* @date 2019/12/10 10:39
*/
@RestController
public class UserController {
@PostMapping("/user")
public JsonResult saveUser(@RequestBody @Validated UserVo userVo){
return JsonResult.success(userVo);
}
}
3.3 全局异常捕获
由于入参校验失败后返回的错误信息不好解析,这里使用全局异常捕获,统一格式返回错误信息
package pers.simon.boot.validator.controller;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import pers.simon.boot.validator.utils.JsonResult;
/**
* @author simon
* @date 2019/12/10 11:09
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 方法参数校验
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public JsonResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return JsonResult.error(e.getBindingResult().getFieldError().getDefaultMessage());
}
}
3.4 测试
使用postman进行接口测试
入参:
{
"userName":"admin",
"birthday": "2019-11-11",
"age":50,
"email":"",
"phone":"17150017489"
}
响应:
{
"code": 1,
"msg": "年龄必须小于30"
}
其它情况自行测试。
四、自定义注解
毕竟提供的注解还是有限的,对于一些特殊校验,可以通过自定义注解实现,下面新增一个校验身份证号的注解
4.1 定义注解
package pers.simon.boot.validator.annotate;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* @author simon
* @date 2019/12/10 11:20
*/
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdCardValidator.class)
public @interface IdCard {
/**
* 提示信息
*/
String message() default "身份证号码不合法";
/**
* 分组信息
*/
Class>[] groups() default {};
/**
* payload 针对于Bean
*/
Class extends Payload>[] payload() default {};
}
4.2 规则校验
package pers.simon.boot.validator.annotate;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* @author simon
* @date 2019/12/10 11:20
*/
public class IdCardValidator implements ConstraintValidator {
@Override
public void initialize(IdCard constraintAnnotation) {
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
//TODO 校验规则自行补充
return false;
}
}
4.3 使用注解
@NotBlank(message = "身份证不能为空")
@IdCard(message = "身份证不合法")
private String idCard;
4.4 测试
入参:
{
"userName":"admin",
"birthday": "2019-11-11",
"age":20,
"email":"[email protected]",
"phone":"17150017489",
"idCard":"123221"
}
响应:
{
"code": 1,
"msg": "身份证不合法"
}
五、分组
同一UserVo在新增和更新情况下,入参的校验规则是不一样的,Validator提供了分组方法完美了解决UserVo复用问题
5.1 创建分组接口
新建分组接口:
package pers.simon.boot.validator.annotate;
import javax.validation.groups.Default;
/**
* @author simon
* @date 2019/12/10 14:10
*/
public interface Create extends Default {
}
更新分组接口:
package pers.simon.boot.validator.annotate;
import javax.validation.groups.Default;
/**
* @author simon
* @date 2019/12/10 14:11
*/
public interface Update extends Default {
}
5.2 入参加入分组参数
@Null(message = "用户ID必须为空",groups = Create.class)
@NotBlank(message = "用户ID不能为空",groups = Update.class)
private String userId;
5.3 Controller在@Validated中加入分组参数
package pers.simon.boot.validator.controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import pers.simon.boot.validator.annotate.Create;
import pers.simon.boot.validator.annotate.Update;
import pers.simon.boot.validator.po.UserVo;
import pers.simon.boot.validator.utils.JsonResult;
/**
*
* @author simon
* @date 2019/12/10 10:39
*/
@RestController
public class UserController {
@PostMapping("/user")
public JsonResult saveUser(@RequestBody @Validated(Create.class) UserVo userVo){
return JsonResult.success(userVo);
}
@PutMapping("/user")
public JsonResult updateUser(@RequestBody @Validated(Update.class) UserVo userVo){
return JsonResult.success(userVo);
}
}
5.4 测试
5.4.1 新增
入参:
{
"userId":"12"
}
响应:
{
"code": 1,
"msg": "用户ID必须为空"
}
5.4.2 更新
入参:
{
"userId":""
}
响应:
{
"code": 1,
"msg": "用户ID不能为空"
}
六、单个参数校验
6.1 在controller 上加 @Validated
package pers.simon.boot.validator.controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import pers.simon.boot.validator.annotate.Create;
import pers.simon.boot.validator.annotate.Update;
import pers.simon.boot.validator.po.UserVo;
import pers.simon.boot.validator.utils.JsonResult;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
*
* @author simon
* @date 2019/12/10 10:39
*/
@RestController
@Validated
public class UserController {
@PostMapping("/user")
public JsonResult saveUser(@RequestBody @Validated(Create.class) UserVo userVo){
return JsonResult.success(userVo);
}
@PutMapping("/user")
public JsonResult updateUser(@RequestBody @Validated(Update.class) UserVo userVo){
return JsonResult.success(userVo);
}
@DeleteMapping("/user")
public JsonResult deleteUser(@NotEmpty(message = "用户ID不能为空")String userId){
return JsonResult.success(userId);
}
}
6.2 增加全局异常捕获
由于单个参数校验抛出的异常是javax.validation.ConstraintViolationException,需增加异常捕获
@ExceptionHandler(ConstraintViolationException.class)
public JsonResult handleMethodArgumentNotValidException(ConstraintViolationException e) {
return JsonResult.error(e.getMessage());
}