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

参考:

相关文章
|
8月前
|
Java Linux 定位技术
Minecraft配置文件参数说明(JAVA服务器篇)
Minecraft JAVA版服务器启动后会生成server.properties配置文件,位于minecraft_server/根目录下。该文件包含多项关键设置,如游戏模式(gamemode)、最大玩家数(max-players)、难度(difficulty)等。此文档详细说明了各配置项的功能与默认值,帮助用户高效管理服务器环境。
1909 60
|
7月前
|
Java
java中一个接口A,以及一个实现它的类B,一个A类型的引用对象作为一个方法的参数,这个参数的类型可以是B的类型吗?
本文探讨了面向对象编程中接口与实现类的关系,以及里氏替换原则(LSP)的应用。通过示例代码展示了如何利用多态性将实现类的对象传递给接口类型的参数,满足LSP的要求。LSP确保子类能无缝替换父类或接口,不改变程序行为。接口定义了行为规范,实现类遵循此规范,从而保证了多态性和代码的可维护性。总结来说,接口与实现类的关系天然符合LSP,体现了多态性的核心思想。
168 0
|
10月前
|
SQL Java 数据库连接
如何用 Java 校验 SQL 语句的合法性?
本文介绍了五种校验 SQL 语句合法性的方案:1) 使用 JDBC API 的 `execute()` 方法,通过捕获异常判断合法性;2) 使用 JSqlParser 库解析 SQL 语句为 Java 对象;3) 使用正则表达式检查 SQL 语句格式;4) 使用 ANTLR 生成 SQL 解析器;5) 使用 Apache Calcite 解析 SQL。每种方法各有优劣,具体选择取决于需求和个人偏好。需要注意的是,这些方法仅能校验语法合法性,无法保证语义正确性,仍需防范 SQL 注入攻击。
421 6
|
12月前
|
Java
实现java执行kettle并传参数
实现java执行kettle并传参数
219 1
|
存储 算法 Java
java制作海报六:Graphics2D的RenderingHints方法参数详解,包括解决文字不清晰,抗锯齿问题
这篇文章是关于如何在Java中使用Graphics2D的RenderingHints方法来提高海报制作的图像质量和文字清晰度,包括抗锯齿和解决文字不清晰问题的技术详解。
447 0
java制作海报六:Graphics2D的RenderingHints方法参数详解,包括解决文字不清晰,抗锯齿问题
|
Java
java构造方法时对象初始化,实例化,参数赋值
java构造方法时对象初始化,实例化,参数赋值
337 1
|
12月前
|
Java
在Java中定义一个不做事且没有参数的构造方法的作用
Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
102 0
【Azure 应用服务】如何查看App Service Java堆栈JVM相关的参数默认配置值?
|
C# 开发者 Windows
震撼发布:全面解析WPF中的打印功能——从基础设置到高级定制,带你一步步实现直接打印文档的完整流程,让你的WPF应用程序瞬间升级,掌握这一技能,轻松应对各种打印需求,彻底告别打印难题!
【8月更文挑战第31天】打印功能在许多WPF应用中不可或缺,尤其在需要生成纸质文档时。WPF提供了强大的打印支持,通过`PrintDialog`等类简化了打印集成。本文将详细介绍如何在WPF应用中实现直接打印文档的功能,并通过具体示例代码展示其实现过程。
1256 0
|
缓存 前端开发 Java
【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(-Xms512m -Xmx1204m)?
【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(-Xms512m -Xmx1204m)?
155 0