Java代码瘦身,巧用 @Valid,@Validated 的分组校验和嵌套检验,实现高阶参数校验操作

这篇具有很好参考价值的文章主要介绍了Java代码瘦身,巧用 @Valid,@Validated 的分组校验和嵌套检验,实现高阶参数校验操作。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Java代码瘦身,巧用 @Valid,@Validated 的分组校验和嵌套检验,实现高阶参数校验操作

导读

        在 JavaEE 项目中, RestFull 层接收参数首先要对一些字段的格式进行校验,以防止所有查询都落到数据库,这也是一种合理的限流手段。以前基本上都是用 if...else...,这样的代码太啰嗦,除了使用策略模式进行优化,今天介绍一下校验注解@Valid,@Validated和@PathVariable,不仅可以减轻代码量,还加强了代码的易读性。


正文

1. @Valid 和 @Validated 区别

        先讲一下这两个注解:@Valid与@Validated都是用来校验接收参数的,如果不使用注解校验参数,那么就需要在业务代码中逐一校验,这样会增加很多的工作量,并且代码不优美。

        刚开始接触的时候多半会被弄混,实际上二者差距还是挺大的。根据自己的项目经验,@Validated和@Valid各有特点,可以联合使用。

Java代码瘦身,巧用 @Valid,@Validated 的分组校验和嵌套检验,实现高阶参数校验操作

  • 提供者

javax.validation.Valid:使用 Hibernate validation 的时候使用,是 JSR-303 规范标准注解支持。如果你是 springboot 项目,那么可以不用单独引入依赖了,因为它就存在于最核心的 web 开发包(spring-boot-starter-web)里面;

org.springframework.validation.annotation.Validated:只用 Spring Validator 校验机制使用,是 Spring 做得一个自定义注解,增强了分组功能;

  • 标注位置

@Validated:可以用在类型、方法和方法参数上,不能用于成员属性(field)上。如果注解在成员属性上,则会报不适用于field的错误;                  

@Valid:可以用在方法、构造函数、方法参数和成员属性(field)上;

  • 分组支持

@Validated:提供分组功能,可以在参数验证时,根据不同的分组采用不同的验证机制;

@Valid:没有分组的功能,不能进行分组校验;

  • 嵌套支持

@Validated:不能进行嵌套对象校验;

@Valid:可以进行嵌套校验,但是,需要在嵌套的字段上面加上注解

2. 常用的校验方法

  • Debug进入jar包,可以看到全量的相关注解:

Java代码瘦身,巧用 @Valid,@Validated 的分组校验和嵌套检验,实现高阶参数校验操作

  • 简述一些常用注解:
注解 使用方法
@AssertFalse 被校验的对象必须为 true
@AssertTrue 被校验的对象必须为 false。
@DecimalMax(value = "val") 被校验的对象必须是数字,而且小于等于 val
@DecimalMin(value = "val") 被校验的对象必须是数字,而且大于等于 val
@Digits(integer = in, fraction = fra) 校验字符串是否是符合指定格式的数字:in 指定整数精度,fra 指定小数精度。
@Future 被校验的对象(日期类型)必须是将来时间,即:比当前时间晚。
@Past :被校验的对象(日期类型)必须是过去时间,即:比当前时间早。
@Size(min = min, max = max) 元素值的在 min 和 max(包含)指定区间之内,如字符长度、集合大小(对于集合来说,null 和空字符串都是算长度的)。

@NotBlank

所注解的元素不能为null且不能为空白,并且必须至少包含一个非空白字符,用于校验CharSequence(含String、StringBuilder和StringBuffer)。只支持字符类型。


@NotEmpty
所注解的元素不能为null且长度大于0,可以是空白,用于校验 CharSequence、数组、Collection 和 Map。

@NotNull
所注解的元素不能为 null,接受任何类型。

@Null
所注解的元素必须为 null,接受任何类型。
@Pattern(regexp = "正则表达式", message = "")

所注解的元素必须匹配指定的正则表达式。

注意:如果 @Pattern 所注解的元素是null,则@Pattern 注解会返回 true,即也会通过校验,所以应该把 @Pattern 注解和 @NotNull 注解结合使用。

3. @Validated分组校验

场景:多个 Restfull 接口共用一个标准 Bean,每个接口的参数相同,但是需要校验的参数(必输项)却不完全相同,这样的场景可以使用 @Validated,因为它提供了分组校验的功能。

分组 说明

隐式分组

1.没有显式分组的默认都是 Default 组;

2.显式分组之后,剩下的那些没有被划分到自建组的字段都属于 Default 组;

3.平常我们写 @Validated注解的时候,不写分组的话默认就是 @Validated(group = {Default.class});

显式分组

1.自定义interface接口的分组,属于自建组;

2.自建组可以继承 Default.class,也可以不继承 Default.class,两者意义不同;

3.多个分组可以一起实用;

4.分组机制让我们可以很灵活的使用对象里面的某些字段,以实现高权限等级参数传递校验等操作。

  • 新建请求对象

@Data
public class TeacherDTO {

    @NotBlank(message = "id必传")
    private String id;

    @NotBlank(message = "不能没有名称")
    private String name;

    @NotNull(message = "age必传")
    private Integer age;

    @NotBlank(message = "不能没有idCard")
    private String idCard;

    @NotBlank(message = "老师不能没有手机号", groups = OnlyTeacher.class)
    private String phone;

    @NotEmpty(message = "学生不能没有书")
    @Size(min = 2, message = "学生必须有两本书", groups = OnlyStudent.class)
    private List<String> bookNames;

    @NotEmpty
    @Size(min = 1, message = "老师不能没有学生", groups = TeacherWithDefault.class)
    private List<String> studentList;
}
  • 新建分组

// Teacher分组
public interface TeacherValid { }

// Student分组
public interface StudentValid { }

// 继承Default的分组
public interface OtherValid extends Default{ }
  • 接口测试

/**
 * Created by tjm on 2022/11/11.
 */
@RestController
@RequestMapping("/test")
public class TestValidController {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestValidController.class);

    /**
     * 测试 - 分组校验 - 默认default
     */
    @PostMapping("/only/default")
    public Object testDefaultValid(@Validated TeacherDTO param, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
        }
        return ResultGenerator.genSuccessResult();
    }
    
    /**
     * 测试 - 分组校验 - 只有teacher
     */
    @PostMapping("/only/teacher")
    public Object testOnlyTeacherValid(@Validated(OnlyTeacher.class) TeacherDTO param, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
        }
        return ResultGenerator.genSuccessResult();
    }

    /**
     * 测试 - 分组校验 - 只有student
     */
    @PostMapping("/only/student")
    public Object testOnlyStudentValid(@Validated(OnlyStudent.class) TeacherDTO param, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
        }
        return ResultGenerator.genSuccessResult();
    }

    /**
     * 测试 - 分组校验 - teacher + default
     */
    @PostMapping("/with/teacher")
    public Object testWithTeacherValid(@Validated({OnlyTeacher.class, Default.class}) TeacherDTO param, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
        }
        return ResultGenerator.genSuccessResult();
    }


    /**
     * 测试 - 分组校验 - 继承default
     */
    @PostMapping("/with/default")
    public Object testWithDefaultValid(@Validated(TeacherWithDefault.class) TeacherDTO param, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
        }
        return ResultGenerator.genSuccessResult();
    }
}
  • 结果

分组 校验参数
只有 default id、name、age、idCard
只有 teacher phone
只有 student booknames
teacher + default id、name、age、idCard、phone
teacher 继承 default id、name、age、idCard、studentList

4.@Valid嵌套校验

  • 新建请求对象
public class Item {

    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private Long id;

    // 嵌套验证必须用 @Valid
    @Valid             
    @NotNull(message = "props不能为空")
    @Size(min = 1, message = "props至少要有一个自定义属性")
    private List<Prop> props;
}

public class Prop {

    @NotNull(message = "pid不能为空")
    @Min(value = 1, message = "pid必须为正整数")
    private Long pid;

    @NotNull(message = "vid不能为空")
    @Min(value = 1, message = "vid必须为正整数")
    private Long vid;

    @NotBlank(message = "pidName不能为空")
    private String pidName;

    @NotBlank(message = "vidName不能为空")
    private String vidName;
}
  • 接口测试
    /**
     * 测试 - 分组校验 - 继承default
     */
    @PostMapping("/item")
    public Object testItemValid(@Validated Item param, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
        }
        return ResultGenerator.genSuccessResult();
    }
  • 测试结果

1. 不仅校验 Item 参数,还会校验子类 Prop 参数;

2. 注意:嵌套验证必须在子参数上用 @Valid。

5.Restfull层@Validated的使用

        校验参数的时候,如何判断并返回失败的结果?一般有两种方式:

  • 全局异常捕获
@ControllerAdvice
@RestController
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 非法参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(value = HttpStatus.OK)
    public ApiResult handleMethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        List<String> list = new ArrayList<>();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        for (FieldError fieldError : fieldErrors) {
            list.add(fieldError.getDefaultMessage());
        }
        Collections.sort(list);
        log.error("fieldErrors" + JSON.toJSONString(list));
        return ApiResult.fail(ApiCode.PARAMETER_EXCEPTION, list);
    }
}
  • 用 BindingResult 在实体类校验信息返回结果绑定

        即使是全局异常捕获的方式,也能看到:校验信息是被封装在 BindingResult 对象里的,所以,我们也可以在 RestFull 层直接取。

1. BindingResult用在实体类校验信息返回结果绑定;

2. BindingResult.hasErrors()判断是否校验通过,bindingResult.getFieldError().getDefaultMessage() 获取在 TestEntity 的属性设置的自定义message,如果没有设置,则返回默认值 "javax.validation.constraints.XXX.message"。

        可以看到,我上面的例子用的都是这种方法,我觉得这样更方便、直观,维护性更好。文章来源地址https://www.toymoban.com/news/detail-443618.html


到了这里,关于Java代码瘦身,巧用 @Valid,@Validated 的分组校验和嵌套检验,实现高阶参数校验操作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何优雅的写代码-替代大量if else的@valid、@validated注解

    @Valid 注解通常用于对象属性字段的规则检测,具体啥意思,下面让我娓娓道来: 下面我们以新增一个员工为功能切入点,以常规写法为背景,慢慢烘托出 @Valid 注解用法详解。 那么,首先,我们会有一个员工对象 Employee,如下 :首先我们会有一个员工对象 Employee,如下 :

    2024年01月18日
    浏览(43)
  • Spring Boot @Validated 和Javax的@Valid配合使用

    @Validation 和@Valid 常常配合使用对传输的参数进行数据校验的注解,并通过配置全局异常处理器进行合理化的提示,增加用户的体验 并且@Validated可以通过分组来指定什么时候触发什么样的参数校验(这里看一下就行,下面有说什么是分组) 其实不用这两个注解也可以完成对传

    2024年02月09日
    浏览(31)
  • Java参数校验@Valid中@Length和@Size的用法和区别

    在Spring框架中,@Length和@Size都是用于参数长度校验的注解,但它们之间存在一些关键的区别: @Length 是Hibernate Validator提供的一个注解,它用于校验字符串的长度。 @Size 也是Hibernate Validator提供的注解,但它可以用于多种数据类型,不仅仅是字符串。对于字符串,它可以校验长

    2024年04月15日
    浏览(39)
  • java如何优雅的实现参数非空校验,快速实现参数非空校验,使用@valid实现参数非空校验

    在java项目接口中,有些必传参数需要进行非空校验,如果参数过多,代码会繁杂且冗余,如何优雅的对参数进行非空校验,下面是实现流程 用实体类接收参数,使用非空注解编辑参数内容 使用 @Valid 注解对参数进行拦截,整体进行非空校验 如果是SpringBoot项目,引入web开发包

    2024年02月08日
    浏览(48)
  • spring-boot 请求参数校验:注解 @Validated 的使用、手动校验、自定义校验

    spring-boot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。 spring-boot已经引入了基础包,所以直接使用就可以。 在属性上添加校验注解: 在Controller上添加 @Validated 注解 校验未通过时,可能看到: 在 @Validated 后面紧跟着追加BindingResult,

    2023年04月16日
    浏览(105)
  • 【优雅的参数验证@Validated】@Validated参数校验的使用及注解详解——你还在用if做条件验证?

    请先看看下面代码:(简单举个例子,代码并不规范) 以上代码主要是为了对用户user实体进行条件验证。 但是那么多的if, 写得纯纯得小白一个,也使得代码显得臃肿不美观不优雅! 接下来,让我们学习使用优雅的参数验证@Validated! @Valid和@Validated是Spring Validation框架提供

    2024年02月02日
    浏览(42)
  • Springboot——@valid 做字段校验和自定义注解

    再项目开发中,针对前端传递的参数信息,有些接口中需要写大量的 if 判断,导致代码臃肿,不够优雅。 此时,可以使用 @Valid 实现基本的字段校验。 springboot 2.3之前 ,直接进行开发即可,无需引用额外的依赖 集成在 spring-boot-starter-web 中。 springboot 2.3之后 需要额外引入

    2023年04月26日
    浏览(56)
  • CSS基础学习--12 分组 和 嵌套 选择器

    一、分组选择器         在样式表中有很多具有相同样式的元素         为了尽量减少代码,你可以使用分组选择器。          每个选择器用逗号分隔 。         在下面的例子中,我们对以上代码使用分组选择器: 备注 :h1、h2与p标签字体颜色都是绿色, 为

    2024年02月09日
    浏览(48)
  • Golang校验字符串是否JSON格式方法json.Valid源码解析

    上篇文章《Golang中如何校验字符串是否为JSON格式?》主要讲解了使用json.Valid校验字符串是否JSON格式的使用方法,本文来剖析一下json.Valid方法的源码。 json.Valid方法定义: scan := newScanner() 获取一个 scanner 类型的对象,关键的是checkValid方法,checkValid源码如下: 首先调用了sc

    2023年04月26日
    浏览(42)
  • element ui form表单循环嵌套 及嵌套表单item的校验方法

    html:  js:  详细的大家可以参考:element ui form循环嵌套表单 及嵌套表单的验证方法(校验)_element循环表单_RxnNing的博客-CSDN博客

    2024年02月09日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包