java参数校验(@Validated、@Valid)使用详解

参考:

https://www.cnblogs.com/virgosnail/p/11556979.html

https://blog.csdn.net/yudian1991/article/details/111146689


概述

介绍及使用

描述:Javax.validation是 spring 集成自带的一个参数校验接口。可通过添加注解来设置校验条件。springboot框架创建web项目后,不需要再添加其他的依赖。

使用:在Controller上使用 @Valid 或 @Validated 注解 开启校验

public String test(@RequestBody @Valid MyRequest req){};

@Validated 和 @Valid 的异同

相同点:

  • 在检验参数符合规范的功能上基本一致;

不同点:

  • 提供者不同:

    • validated 是Spring Validation验证框架对参数的验证机制;
    • valid是 javax 提供的参数验证机制
  • 作用域不同:

    • validated :类,方法,参数

    • valid:方法,字段,构造方法,参数,TYPE_US

      注:TYPE_USE:在 Java 8 之前的版本中,只能允许在声明式前使用 Annotation。而在 Java 8 版本中,Annotation 可以被用在任何使用 Type 的地方,例如:初始化对象时 (new),对象类型转化时,使用 implements 表达式时,或者使用 throws 表达式时。

      //初始化对象时
      String myString = new @Valid String();
      
      //对象类型转化时
      myString = (@Valid String) str;
      
      //使用 implements 表达式时
      class MyList<T> implements List<@Valid T> {...}
      
      //使用 throws 表达式时
      public void validateValues() throws @Valid ValidationFailedException{...}
      

参数校验常用注解

除了前四个 @Null,@ NotNull,@ NotBlank,@NotEmpty外,其他所有的注解,传 null 时都会被当作有效处理

注解常用参数值:message(校验不通过反馈信息)

JSR303定义的基础校验类型:

注解 验证的数据类型 备注
Null 任意类型 参数值必须是 Null
NotNull 任意类型 参数值必须不是 Null
NotBlank 只能作用于字符串 字符串不能为 null,而且字符串长度必须大于0,至少包含一个非空字符串
NotEmpty CharSequence
Collection
Map
Array
参数值不能为null,且不能为空
(字符串长度必须大于0,空字符串(“ ”)可以通过校验)
Size(min,max ) CharSequence
Collection
Map
Array
字符串:字符串长度必须在指定的范围内
Collection:集合大小必须在指定的范围内
Map:map的大小必须在指定的范围内
Array:数组长度必须在指定的范围内
Pattern(regexp) 字符串类型 验证字符串是否符合正则表达式
Min(value) 整型类型 参数值必须大于等于 最小值
Max(value) 整型类型 参数值必须小于等于 最大值
DecimalMin(value) 整型类型 参数值必须大于等于 最小值
DecimalMax(value) 整型类型 参数值必须小于等于 最大值
Positive 数字类型 参数值为正数
PositiveOrZero 数字类型 参数值为正数或0
Negative 数字类型 参数值为负数
NegativeOrZero 数字类型 参数值为负数或0
Digits(integer,fraction) 数字类型 参数值为数字,且最大长度不超过integer位,整数部分最高位不超过fraction位
AssertTrue 布尔类型 参数值必须为 true
AssertFalse 布尔类型 参数值必须为 false
Past 时间类型(Date) 参数值为时间,且必须小于 当前时间
PastOrPresent 时间类型(Date) 参数值为时间,且必须小于或等于 当前时间
Future 时间类型(Date) 参数值为时间,且必须大于 当前时间
FutureOrPresent 时间类型(Date) 参数值为时间,且必须大于或等于 当前日期
Email 字符串类型 被注释的元素必须是电子邮箱地址

Hibernate Validator 中附加的 constraint :

注解 验证的数据类型 备注
Length 字符串类型 字符串的长度在min 和 max 之间
Range 数字类型
字符串类型
数值或者字符串的值必须在 min 和 max 指定的范围内

Pattern注解校验 常用正则表达式

@Pattern(regexp = "^[1-9]]\\d*$", message = "XX参数值必须是正整数")

高阶使用

自定义分组校验

有时多个场景接口公用一个请求对象,不同业务场景对请求对象的参数校验需求不同,可以使用分组校验来解决

注意:

  • 没有指定显示分组的被校验字段和校验注解,默认都是 Default 组(即 Default.class)

  • 若自定义的分组接口未继承 Default 分组,且 @Validated(或 @Valid)注解未传参 Default.class,则只会校验请求对象中进行了显示分组的字段,不会校验默认分组(没有进行显示分组)的字段

    自定义的分组接口不继承 Default 分组 + @Validated(或 @Valid)注解传参 {自定义分组接口.class, Default.class}

    = 自定义的分组接口继承 Default 分组 + @Validated(或 @Valid)注解只传参自定义分组接口


示例:

  • 新建自定义分组校验接口

    public interface Student {
    }
    
    import javax.validation.groups.Default;
    
    public interface Teacher extends Default {
    }
    
  • 新建请求对象

    @Data
    public class UserDTO {
    
        @NotBlank(message = "不能没有名称")
        private String name;
    
        @NotBlank(message = "老师不能没有手机号", groups = Teacher.class)
        private String phone;
    
        @NotEmpty(message = "学生不能没有书", groups = Student.clas)
        @Size(min = 2, message = "学生必须有两本书", groups = Student.class)
        private List<String> bookNames;
    }
    
  • Controller

    @RestController
    public class ValidatedController {
        
        /**
         * 测试 校验student分组+默认分组
         */
        @PostMapping("student")
        public UserDTO validatedStudent(@Validated(value = {Student.class, Default.class}) @RequestBody UserDTO userDTO) {
            return userDTO;
        }
    
        /**
         * 测试 校验student分组+默认分组
         */
        @PostMapping("teacher")
        public UserDTO validatedTeacher(@Validated(value = {Teacher.class}) @RequestBody UserDTO userDTO) {
            return userDTO;
        }
    
        /**
         * 测试 分组校验  default
         */
        @PostMapping("default")
        public UserDTO validatedDefault(@Validated(value = {Default.class}) @RequestBody UserDTO userDTO) {
            return userDTO;
        }
    
        /**
         * 测试 分组校验 onlyStudent
         */
        @PostMapping("onlyStudent")
        public UserDTO validatedOnlyStudent(@Validated(value = {Student.class}) @RequestBody UserDTO userDTO) {
            return userDTO;
    }
    

自定义校验注解

定义注解

@Documented
//指定注解的处理类
@Constraint(validatedBy = {VersionValidatorHandler.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface ConstantVersion {
 
   String message() default "{constraint.default.const.message}";
 
   Class<?>[] groups() default {};
 
   Class<? extends Payload>[] payload() default {};
 
   String value();
 
}

注解处理类

public class VersionValidatorHandler implements ConstraintValidator<Constant, String> {
 
    private String constant;
 
    @Override
    public void initialize(Constant constraintAnnotation) {
        //获取设置的字段值
        this.constant = constraintAnnotation.value();
    }
 
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        //判断参数是否等于设置的字段值,返回结果
        return constant.equals(value);
    }
}

自定义注解使用

@ConstantVersion (message = "verson只能为1.0.0",value="1.0.0")
String version;

Controller

@RestController
public class TestController {

    @RequestMapping("/test")
    public String createUser(@Valid User user, BindingResult bindingResult){
        if (bindingResult.hasErrors()){
            return bindingResult.getFieldError().getDefaultMessage();
        }
        return  "success";
    }
}

嵌套检验

描述:当对象 Man 的字段 houses 包含 House 对象类型时,在检验 houses 字段时可以检验 House 对象的属性字段时,就称为嵌套检验

方案:在被检验的字段上添加 @Valid 注解就可以实现嵌套检验


示例如下:

  • 在检验 Man 对象的 houses 字段时,在houses 字段上添加 @Valid 注解后,就可以检验 list 中的 House 的属性是否符合要求;

  • 否则只会检验 houses 的集合大小是否大于1,不会校验集合中的 House 对象,比如 House 对象的 name 长度是否符合要求。

class Man{
    @Valid
    @Size(min = 1)
    private List<House> houses;
}

class House{
    @Length(min = 1,max = 10)
    private String name;
}

拓展

异常处理

参数校验异常:MethodArgumentNotValidException

方式一:基于异常监听@ControllerAdvice(参考:https://www.cnblogs.com/gezi0815/p/13815397.html)

/**
* 全局异常处理器
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
 
    /**
     * 异常处理
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public DataResult exceptionHandler(Exception e) {
        log.error("GlobalExceptionHandler.exceptionHandler , 异常信息",e);
        return DataResult.fail(e.getMessage());
    }
 
    /**
     * 业务异常
     */
    @ResponseBody
    @ExceptionHandler(value = BplCommonException.class)
    public DataResult bplCommonExceptionHandler(BplCommonException e) {
        log.warn("",e);
        return DataResult.fail(e.getMessage());
    }
 
    /**
     * 处理所有RequestBody注解参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public DataResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        log.warn(e.getMessage());
        return DataResult.fail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }
 
    /**
     * 处理所有RequestParam注解数据验证异常
     */
    @ExceptionHandler(BindException.class)
    public DataResult handleBindException(BindException ex) {
        FieldError fieldError = ex.getBindingResult().getFieldError();
        log.warn("必填校验异常:{}({})", fieldError.getDefaultMessage(),fieldError.getField());
        return DataResult.fail(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }
}

方式二:基于Handle或Filter(参考:https://blog.51cto.com/u_12012821/2511625)

if (e instanceof MethodArgumentNotValidException) {
	String errorMsg = ((MethodArgumentNotValidException) e)
		.getBindingResult()
		.getAllErrors()
        .stream()
		.map(DefaultMessageSourceResolvable::getDefaultMessage)
		.collect(Collectors.joining(","));
    
	resp = R.builder()
		.code(ResultCodeEnum.BUSINESS_ERROR.getCode())
		.message(errorMsg).success(false)
		.build();
} 

你可能感兴趣的:(Java基础,java,参数校验,Validated,Valid)