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();
} 

参考:

相关文章
|
2月前
|
Java
实现java执行kettle并传参数
实现java执行kettle并传参数
34 1
|
2月前
|
Java
在Java中定义一个不做事且没有参数的构造方法的作用
Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
|
3月前
|
存储 算法 Java
java制作海报六:Graphics2D的RenderingHints方法参数详解,包括解决文字不清晰,抗锯齿问题
这篇文章是关于如何在Java中使用Graphics2D的RenderingHints方法来提高海报制作的图像质量和文字清晰度,包括抗锯齿和解决文字不清晰问题的技术详解。
105 0
java制作海报六:Graphics2D的RenderingHints方法参数详解,包括解决文字不清晰,抗锯齿问题
|
3月前
|
Java
java构造方法时对象初始化,实例化,参数赋值
java构造方法时对象初始化,实例化,参数赋值
96 1
|
5月前
|
Java
【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
|
5月前
|
C# 开发者 Windows
震撼发布:全面解析WPF中的打印功能——从基础设置到高级定制,带你一步步实现直接打印文档的完整流程,让你的WPF应用程序瞬间升级,掌握这一技能,轻松应对各种打印需求,彻底告别打印难题!
【8月更文挑战第31天】打印功能在许多WPF应用中不可或缺,尤其在需要生成纸质文档时。WPF提供了强大的打印支持,通过`PrintDialog`等类简化了打印集成。本文将详细介绍如何在WPF应用中实现直接打印文档的功能,并通过具体示例代码展示其实现过程。
461 0
|
5月前
|
缓存 前端开发 Java
【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(-Xms512m -Xmx1204m)?
【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(-Xms512m -Xmx1204m)?
|
5月前
|
运维 监控 Java
【JVM 调优秘籍】实战指南:JVM 调优参数全解析,让 Java 应用程序性能飙升!
【8月更文挑战第24天】本文通过一个大型在线零售平台的例子,深入探讨了Java虚拟机(JVM)性能调优的关键技术。面对应用响应延迟的问题,文章详细介绍了几种常用的JVM参数调整策略,包括堆内存大小、年轻代配置、垃圾回收器的选择及日志记录等。通过具体实践(如设置`-Xms`, `-Xmx`, `-XX:NewRatio`, `-XX:+UseParallelGC`等),成功降低了高峰期的响应时间,提高了系统的整体性能与稳定性。案例展示了合理配置JVM参数的重要性及其对解决实际问题的有效性。
147 0
|
5月前
|
Java 编译器 数据库连接
Java中的无参数构造方法
Java中的无参数构造方法
203 0
|
5月前
|
Java
JAVA 获取 URL 指定参数的值
JAVA 获取 URL 指定参数的值
58 0