抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

通常我们在使用 spring 框架编写接口时,对于部分接口的参数我们要进行判空或者格式校验来避免程序出现异常。
那时我们一般都是使用 if-else 逐个对参数进行校验。这种方法按逻辑来说也是没有问题的,同样也能实现预期效果。
但是,这样的代码从可读性以及美观程序来看,是非常糟糕的。
那么,我们就可以使用@valid 注解来帮助我们优雅的校验参数。

一、如何使用 Validation 相关注解进行参数校验

① 为实体类中的参数或者对象添加相应的注解;

② 在控制器层进行注解声明,或者手动调用校验方法进行校验;

③ 对异常进行处理;

二、Validation 类的相关注解及描述

嵌套注解 @Valid(javax.validation.Valid)

空和非空检查: @Null、@NotNull、@NotBlank、@NotEmpty

注解 支持 Java 类型 备注
@Null Object 验证元素值为 null
@NotNull Object 验证元素值不能为 null
@NotBlank CharSequence 验证元素值不为 null 且移除两边空格后长度大于 0
@NotEmpty CharSequence,Collection,Map and Arrays 验证元素值不为 null 且不为空(字符串长度不为 0、集合大小不为 0)

Boolean 值检查: @AssertTrue、@AssertFalse

注解 支持 Java 类型 备注
@AssertTrue Boolean, boolean 验证元素值必须为 true,否则抛异常
@AssertFalse Boolean, boolean 验证元素值必须为 flase

长度检查: @Size、@Length

注解 支持 Java 类型 备注
@Size String,Collection,Map,arrays,CharSequence 验证元素个数包含在一个区间

日期检查: @Future、@FutureOrPresent、@Past、@PastOrPresent

注解 支持 Java 类型 备注
@Future java.util.Date, java.util.Calendar 验证日期为当前时间之后
@FutureOrPresent java.util.Date, java.util.Calendar 验证日期为当前时间或之后一个时间
@Past java.util.Date, java.util.Calendar 验证日期为当前时间之前
@PastOrPresent java.util.Date, java.util.Calendar 验证日期为当前时间或之前

其它检查: @Email、@CreditCardNumber、@URL、@Pattern、@ScriptAssert、@UniqueElements

注解 支持 Java 类型 备注
@Email CharSequence 验证邮箱
@CreditCardNumber CharSequence 验证信用卡
@URL CharSequence 验证 url
@Pattern CharSequence 验证正则,如:@Pattern(regexp = “^[1][3,4,5,6,7,8,9][0-9]{9}$”, message = “手机号格式有误”)
@Valid Object 验证关联对象元素进行递归校验检查
@UniqueElements Collection 校验集合中的元素必须保持唯一 否则异常

数值检查: @Min、@Max、@Range、@DecimalMin、@DecimalMax、@Digits

注解 支持 Java 类型 备注
@Min BigDecimal, BigInteger, byte, short,int, long,Number,String(数字字符串) 检验当前数值大于等于指定值
@Max BigDecimal, BigInteger, byte, short,int, long,Number,String(数字字符串) 检验当前数值小于等于指定值
@DecimalMin BigDecimal, BigInteger, byte, short,int, long,Number,String(数字字符串) 验证数值是否大于等于指定值
@DecimalMax BigDecimal, BigInteger, byte, short,int, long,Number,String(数字字符串) 验证数值是否小于等于指定值
@Digits(integer = 3, fraction = 2) 验证注解的元素值的整数位数和小数位数上限

PS:@Valid 和@Validated 的区别

  • @Valid 能作用在属性上,支持嵌套验证,不支持分组
  • @Validated 时@Valid 的封装,不能作用属性上,不支持嵌套,支持分组
  • 若分组需要有序进行,比如分组 1 后才校验分组 2,可定义第三个接口 Three 使用@GroupSequence({First.class,Second.class}) ,@Validated 指定分组 Three.class

三、使用 Validation API 进行参数效验步骤

第一种:在 Controller 方法参数前加@Valid 注解——校验不通过时直接抛异常,get 请求直接在平面参数前添加相应的校验规则注解,使用这种的话一般结合统一异常处理进行处理;

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

/**
  * 将校验结果放进BindingResult里面,用户自行判断并处理
  *
  * @param userInfo
  * @param bindingResult
  * @return
  */
 @PostMapping("/testBindingResult")
 public String testBindingResult(@RequestBody @Valid UserInfo userInfo, BindingResult bindingResult) {
     // 参数校验
     if (bindingResult.hasErrors()) {
         String messages = bindingResult.getAllErrors()
                 .stream()
                 .map(ObjectError::getDefaultMessage)
                 .reduce((m1, m2) -> m1 + ";" + m2)
                 .orElse("参数输入有误!");
         //这里可以抛出自定义异常,或者进行其他操作
         throw new IllegalArgumentException(messages);
     }
     return "操作成功!";

 }

这里我们是直接抛出了异常,如果没有进行全局异常处理的话,接口将会返回如下信息:
在这里插入图片描述

四、SpringBoot 项目中实战演练

Spring Boot 2.3 后,starter-web 不再包含子依赖 starter-validation,需要手动导入。

1.对实体类的变量进行注解标注

实体类中添加 @Valid 相关验证注解,并在注解中添加出错时的响应消息。

@Data
public class User {

    @NotBlank(message = "姓名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Length(min = 6, max = 16, message = "密码长度为6-16位")

    private String password;
    @Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}", message = "手机号格式不正确")
    private String phone;

    // 嵌套必须加 @Valid,否则嵌套中的验证不生效
    @Valid
    //message为验证后的返回消息
    @NotNull(message = "userinfo不能为空")
    private UserInfo userInfo;

}

2.创建自定义异常

自定义异常类,方便我们处理手动抛出的异常。

public class ParamaErrorException extends RuntimeException {
    public ParamaErrorException() {

    }
    public ParamaErrorException(String message) {
        super(message);
    }
}

3.自定义响应枚举类

定义一个返回信息的枚举类,方便我们快速响应信息,不必每次都写返回消息和响应码。

public enum ResultEnum {
    SUCCESS(1000, "请求成功"),
    PARAMETER_ERROR(1001, "请求参数有误!"),
    UNKNOWN_ERROR(9999, "未知的错误!");
    private Integer code;
    private String message;
    ResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
    public Integer getCode() {
        return code;
    }
    public String getMessage() {
        return message;
    }
}

4.自定义响应对象类

创建用于返回调用方的响应信息的实体类。

@Data
public class ResponseResult {
    private Integer code;
    private String msg;
    public ResponseResult() {}
    public ResponseResult(ResultEnum resultEnum) {
        this.code = resultEnum.getCode();
        this.msg = resultEnum.getMessage();
    }

    public ResponseResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

5.接口类中添加相关注解

处理 get 请求直接在参数前添加验证注解,处理 post 请求时在对象前添加@Valid 注解

@Validated
@RestController
@Api(value = "测试使用validation验证参数")
public class TestController {
    /**
     * 测试get方法,手动if进行判空,校验失败时手动抛出自定义异常
     *
     * @param username 姓名
     * @return ResponseResult
     */
    @ApiOperation(value = "测试get方法", notes = "输入用户名")
    @GetMapping("/testGet")
    public ResponseResult testGet(String username) {
        if (username == null || "".equals(username)) {
            throw new ParamaErrorException("username 不能为空");
        }
        return new ResponseResult(ResultEnum.SUCCESS);
    }

    /**
     * 使用注解校验get请求平面参数,需要在Controller类头部添加@Validated注解,否则不能成功校验,这种方法不用手动抛出异常
     *
     * @param username
     * @return
     */
    @ApiOperation(value = "测试get方法", notes = "输入用户名")
    @GetMapping("/testGetByValidated")
    public ResponseResult testGetByValidated(@Length(max = 4) @RequestParam("username") String username) {
        return new ResponseResult(ResultEnum.SUCCESS);
    }

    /**
     * post方法传入单个对象进行校验,在参数前添加@Valid注解,校验失败时会抛出异常并使用全局异常进行处理
     *
     * @param userInfo 用户信息
     * @return ResponseResult
     */
    @ApiOperation(value = "post方法传入单个对象", notes = "传入json对象")
    @PostMapping("/testUserInfo")
    public ResponseResult testUserInfo(@Valid @RequestBody UserInfo userInfo) {
        return new ResponseResult(ResultEnum.SUCCESS);
    }

    /**
     * post方法传入对象,手动校验,此时参数前没有添加@Valid注解,所以不会自动进行校验,手动调用validate方法进行校验,失败时会抛出异常
     *
     * @param userInfo
     * @return ResponseResult
     */
    @ApiOperation(value = "post方法传入对象,手动测试", notes = "单个对象")
    @PostMapping("/checkByMethod")
    public ResponseResult checkByMethod(@RequestBody UserInfo userInfo) {
        //调用api校验
        MyValidationUtils.validate(userInfo);
        return new ResponseResult(ResultEnum.SUCCESS);
    }

    /**
     * post方法传入多个对象,当使用@Valid校验对象集合时,要在控制层添加@Validated注解,否则不会对集合中的每个对象进行校验
     *
     * @param userInfo
     * @return ResponseResult
     */
    @ApiOperation(value = "post方法传入多个对象", notes = "多个对象")
    @PostMapping("/testUserList")
    public ResponseResult testUserList(@Valid @RequestBody List<UserInfo> userInfo) {
        return new ResponseResult(ResultEnum.SUCCESS);
    }

    /**
     * 测试对象中嵌套对象的情况,此时也要在对象属性上添加@Valid注解
     *
     * @param user
     * @return
     */
    @ApiOperation(value = "测试对象中嵌套对象的情况")
    @PostMapping("/checkUser")
    public ResponseResult checkUser(@Valid @RequestBody User user) {
        return new ResponseResult(ResultEnum.SUCCESS);
    }

    /**
     * 将校验结果放进BindingResult里面,用户自行判断并处理
     *
     * @param userInfo
     * @param bindingResult
     * @return
     */
    @PostMapping("/testBindingResult")
    public String testBindingResult(@RequestBody @Valid UserInfo userInfo, BindingResult bindingResult) {
        // 参数校验
        if (bindingResult.hasErrors()) {
            String messages = bindingResult.getAllErrors()
                    .stream()
                    .map(ObjectError::getDefaultMessage)
                    .reduce((m1, m2) -> m1 + ";" + m2)
                    .orElse("参数输入有误!");
            //这里可以抛出自定义异常,或者进行其他操作
            throw new IllegalArgumentException(messages);
        }
        return "操作成功!";
    }
}

补充:使用自定义参数注解 1.我们这里创建一个身份证校验注解

@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})        // 作用位置
@Retention(RetentionPolicy.RUNTIME)  // 运行时机
@Constraint(validatedBy = IdentityCardNumberValidator.class)    // 标明这个校验注解是使用哪个校验器进行校验的,在这里指定或者在初始化的时候指定
public @interface IdentityCardNumber {
    String message() default "身份证号码不合法";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

这个注解是作用在 Field 字段上,运行时生效,触发的是 IdentityCardNumber 这个验证类。

/**
  *
  *  自定义校验器
  *  首先必须实现 ConstraintValidator 接口, 其中第一个参数是 自定义校验注解,第二个参数是校验的类型
  */
public class ListValConstraintValidator implements ConstraintValidator<ListValue, Integer> {
    Set<Integer> set = new HashSet<>();
    // 初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals(); // vals是注解中的设置的固定参数
        for (int val : vals) {
            set.add(val);
        }
    }
    /**
     * 进行校验
     * @param value  提交的需要被校验的值
     * @param context 上下文环境
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}
message 定制化的提示信息,主要是从ValidationMessages.properties里提取,也可以依据实际情况进行定制
groups 这里主要进行将validator进行分类,不同的类group中会执行不同的validator操作
payload 主要是针对bean的,使用不多。

评论