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

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

概述

介绍及使用

描述: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 CharSequenceCollectionMapArray 参数值不能为null,且不能为空(字符串长度必须大于0,空字符串(“ ”)可以通过校验)
Size(min,max ) CharSequenceCollectionMapArray 字符串:字符串长度必须在指定的范围内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(参考:优雅的java参数校验

/**
* 全局异常处理器
*/
@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();
} 

参考:

相关文章
|
1月前
|
存储 Java
Java-参数传值
Java-参数传值
25 4
|
1月前
|
Java
【Java每日一题】— —第二十二题:类名作参数进行方法调用的传递问题。
【Java每日一题】— —第二十二题:类名作参数进行方法调用的传递问题。
26 1
|
4月前
|
数据采集 Java 数据格式
【Java】正则表达式,校验数据格式的合法性。
【Java】正则表达式,校验数据格式的合法性。
59 0
|
4月前
|
前端开发 Java 数据库连接
9:参数校验-Java Spring
9:参数校验-Java Spring
32 0
|
1月前
|
存储 Java fastjson
Java泛型-4(类型擦除后如何获取泛型参数)
Java泛型-4(类型擦除后如何获取泛型参数)
33 1
|
29天前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
79 0
|
3天前
|
存储 Java 测试技术
一文搞清楚Java中的方法、常量、变量、参数
在JVM的运转中,承载的是数据,而数据的一种变现形式就是“量”,量分为:**常量与变量**,我们在数学和物理学中已经接触过变量的概念了,在Java中的变量就是在程序运行过程中可以改变其值的量。
14 0
|
15天前
|
存储 监控 安全
泛型魔法:解码Java中的类型参数
泛型魔法:解码Java中的类型参数
34 0
泛型魔法:解码Java中的类型参数
|
1月前
|
前端开发 Java Spring
Java 新手如何使用Spring MVC 中的查询字符串和查询参数
Java 新手如何使用Spring MVC 中的查询字符串和查询参数
|
2月前
|
NoSQL Java Redis
【Java专题_09】生产环境Jvm参数设置
【Java专题_09】生产环境Jvm参数设置