码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • JSR303数据校验


    目录

    前言

    一、引入依赖

    二、开启校验功能 

    三、全局异常处理

    四、分组校验

    五、自定义校验

    总结



    前言

    数据校验是一个我们开发过程中经常用到的功能,不论是前端还是后端,都需要对数据进行校验,所以,我们有必要总结一些常用方式。


    一、引入依赖

    首先,我们需要引入数据校验所需要的依赖:

    1. org.springframework.boot
    2. spring-boot-starter-validation

    引入该starter之后,我们就能很轻松的完成数据校验工作,只需要在想要校验的实体类的字段上加上对应的校验注解即可对该字段进行校验规则设置。

    1. @Getter
    2. @Setter
    3. @TableName("pms_brand")
    4. @ApiModel(description = "品牌表")
    5. public class PmsBrand implements Serializable {
    6. private static final long serialVersionUID = -21640018136618420L;
    7. @TableId(value = "brand_id", type = IdType.ASSIGN_ID)
    8. @ApiModelProperty("品牌id")
    9. private Long brandId;
    10. @NotBlank(message = "品牌名不能为空")
    11. @ApiModelProperty("品牌名")
    12. private String name;
    13. @URL(message = "需要是一个合法的URL")
    14. @NotBlank(message = "logo地址不能为空")
    15. @ApiModelProperty("品牌logo")
    16. private String logo;
    17. }

    到这里我们就完成了初步的校验规则设置工作。 

     此处需要特别注意的是:@URL校验注解只在该字段有值时有效,没值或者为空时无效,所以,如果我们想在该字段没值或者为空时也进行校验则可以另外添加一个 @NotBlank 注解

    二、开启校验功能 

    开启校验功能只需要在Controller层方法中的需要校验的实体参数前添加一个 @Valid 注解即可

    1. @PostMapping("/insert")
    2. public R insert(@Valid @RequestBody PmsBrand pmsBrand) {
    3. return R.ok(this.pmsBrandService.save(pmsBrand));
    4. }

     至此,我们就完成了数据校验的基本实现配置,也就是说,配置到这里,我们就能实现数据校验了,但是,当数据校验过程中发现数据校验不通过而抛出异常时,我们并不能很好的知道哪些数据校验失败,以及校验失败的信息是啥?为了解决这个问题,我们通常可以使用基于AOP的全局异常处理来帮助我们进行一些异常信息的处理。

    三、全局异常处理

    1. @Slf4j
    2. @RestControllerAdvice(basePackages = "com.boot.shop.controller")
    3. public class ValidationExceptionHandler {
    4. @ExceptionHandler(value = MethodArgumentNotValidException.class)
    5. public R validationException(MethodArgumentNotValidException e){
    6. log.error("出现了数据校验异常,异常类型为:{}.异常信息为:{}",e.getClass(),e.getMessage());
    7. // 从特定异常中拿到异常结果集
    8. BindingResult bindingResult = e.getBindingResult();
    9. // 从异常结果集中拿到字段校验异常信息
    10. List fieldErrors = bindingResult.getFieldErrors();
    11. Map errorMap = new HashMap<>();
    12. fieldErrors.forEach(error -> errorMap.put(error.getField(),error.getDefaultMessage()));
    13. System.out.println(errorMap.size());
    14. return R.fail(errorMap,"数据校验失败!");
    15. }
    16. }

    需要注意的几点是:1、建议要在@RestControllerAdvice注解中添加包扫描路径

                                    2、对于数据校验异常推荐将@ExceptionHandler异常处理器的目标异常设置为MethodArgumentNotValidException.class,因为这个异常对象中封装了数据校验的一些信息。

    四、分组校验

    场景:当我们插入一条数据时,我们可能需要对很多字段进行校验,但在修改时,我们可能只需要对修改的相关字段进行校验,此外,也有可能存在,添加时需要校验而修改不需要校验等情况,所以,我们应该针对不同的操作场景使用不同的校验规则,以此来确保精准校验。

    首先,如下图新建两个校验分组,这两个分组都是空接口,只是为了作为分组标识进行区分。没有实际内容。

     然后,基于不同的操作,设置字段在不同操作场景下的归属的分组,设置好分组之后,

    1. @Getter
    2. @Setter
    3. @TableName("pms_brand")
    4. @ApiModel(description = "品牌表")
    5. public class PmsBrand implements Serializable {
    6. private static final long serialVersionUID = -21640018136618420L;
    7. @NotNull(message = "修改时id必须指定",groups = {Update.class})
    8. @Null(message = "新增时id不能指定,因为id自增",groups = {Insert.class})
    9. @TableId(value = "brand_id", type = IdType.AUTO)
    10. @ApiModelProperty("品牌id")
    11. private Long brandId;
    12. @NotBlank(message = "品牌名不能为空",groups = {Insert.class, Update.class})
    13. @ApiModelProperty("品牌名")
    14. private String name;
    15. @URL(message = "需要是一个合法的URL",groups = {Insert.class, Update.class})
    16. @NotBlank(message = "logo地址不能为空")
    17. @ApiModelProperty("品牌logo")
    18. private String logo;
    19. }

     在对实体类对应的字段设置完分子之后,在Controller层方法的校验实体类前加上注解@Validated

    并指定分组为Insert ;至此,当新增数据时,将使用insert新增分组校验规则,代码如下:

    1. @PostMapping("/insert")
    2. public R insert(@Validated(value = {Insert.class}) @RequestBody PmsBrand pmsBrand) {
    3. return R.ok(this.pmsBrandService.save(pmsBrand));
    4. }

     特别注意:一旦我们在注解@Validated中指定了校验分组,那么对于哪些没有指定校验分组的字段将不再生效。例如下面的这个字段,@NotBlank(message = "logo地址不能为空") 校验由于没有指定校验分组,而@Validated又指定了Insert分组,所以,该校验无效。

    1. @URL(message = "需要是一个合法的URL",groups = {Insert.class, Update.class})
    2. @NotBlank(message = "logo地址不能为空")
    3. @ApiModelProperty("品牌logo")
    4. private String logo;

    五、自定义校验

     首先,我们需要知道的是,几乎所有的校验都是基于JSR303规则实现的,所以,如果我们想要自定义一个校验注解,我们也必须遵循JSR303的校验规则。至于该如何自定义,我们可以参考已有的校验注解实现。例如 @NotBlank

    1. @Documented
    2. @Constraint(validatedBy = { })
    3. @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    4. @Retention(RUNTIME)
    5. @Repeatable(List.class)
    6. public @interface NotBlank {
    7. String message() default "{javax.validation.constraints.NotBlank.message}";
    8. Class[] groups() default { };
    9. Classextends Payload>[] payload() default { };
    10. }

     可以看到的是,该校验注解除了一些元注解外,还包含了一个@Constraint(validatedBy = { })特殊的注解,起作用就是为了方便进行自定义校验实现的。我们是需要将我们自定实现的校验器传入其中就能够实现自定义校验。

    想要实现自定义校验需要进行3个步骤的操作:

            1)添加一个自定义的校验注解

            2)编写一个自定义的校验器

            3)将自定义检验注解和自定义校验器关联

    那么,现在我们进行第一步,添加一个自定义校验注解:

    1. @Documented
    2. @Constraint(validatedBy = { })
    3. @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    4. @Retention(RUNTIME)
    5. public @interface ListValue {
    6. String message() default "{com.boot.shop.annotation.ListValue.message}";
    7. Class[] groups() default { };
    8. Classextends Payload>[] payload() default { };
    9. // 自定义属性
    10. int[] value() default { };
    11. }

     为了能够使校验失败时,提示我们自定义的提示信息,我们可以自己指定默认的message取值路径,如上图com.boot.shop.annotation.ListValue.message我就是取的自己定义的值,同时该值的配置需要符合JSR303的配置规范,也就是,我们需要在resource目录下创建一个校验提示信息配置文件,如下图所示:


     此外,如果存在properties配置文件乱码情况时,可以如下配置:

     第二步、编写一个对应的校验器,在编写之前,我们先看一下@Constraint(validatedBy = { })该注解需要我们传入一个什么样的校验器,源码如下:

    1. @Documented
    2. @Target({ ANNOTATION_TYPE })
    3. @Retention(RUNTIME)
    4. public @interface Constraint {
    5. /**
    6. * 实现约束的 ConstraintValidator 类。
    7. * 给定的类必须为给定的 ValidationTarget 引用不同的目标类型。
    8. * 如果两个 ConstraintValidators 引用同一个类型,就会发生异常。
    9. * 最多接受一个针对方法或构造函数的参数数组(也称为交叉参数)的 ConstraintValidator。
    10. * 如果存在两个或更多,则会发生异常。
    11. *
    12. * 返回:实现约束的 ConstraintValidator 类数组
    13. */
    14. Classextends ConstraintValidator>[] validatedBy();
    15. }

     可以看到的是,我们传入的是一个ConstraintValidator类型的类对象,但ConstraintValidator本身是一个接口,所以,我们需要传入一个他的实现类。在这个实现类中实现他的校验方法isValid

    ConstraintValidator源码如下,值得注意的是,在实现该接口时,我们需要指定校验器关联的校验注解和校验的数据类型

    1. public interface ConstraintValidatorextends Annotation, T> {
    2. /**
    3. * Initializes the validator in preparation for
    4. * {@link #isValid(Object, ConstraintValidatorContext)} calls.
    5. * The constraint annotation for a given constraint declaration
    6. * is passed.
    7. *

    8. * This method is guaranteed to be called before any use of this instance for
    9. * validation.
    10. *

    11. * The default implementation is a no-op.
    12. *
    13. * @param constraintAnnotation annotation instance for a given constraint declaration
    14. */
    15. default void initialize(A constraintAnnotation) {
    16. }
    17. /**
    18. * Implements the validation logic.
    19. * The state of {@code value} must not be altered.
    20. *

    21. * This method can be accessed concurrently, thread-safety must be ensured
    22. * by the implementation.
    23. *
    24. * @param value object to validate
    25. * @param context context in which the constraint is evaluated
    26. *
    27. * @return {@code false} if {@code value} does not pass the constraint
    28. */
    29. boolean isValid(T value, ConstraintValidatorContext context);
    30. }

     第三步、实现一个校验器

    1. public class CustomeConstraintValidator implements ConstraintValidator {
    2. private final Set values = new HashSet<>();
    3. // 该初始化方法将自定义注解的详细信息封装成constraintAnnotation对象,从中我们可以取到一些我们想要的信息,比如注解的数据值
    4. @Override
    5. public void initialize(ListValue constraintAnnotation) {
    6. for (int value : constraintAnnotation.value()) {
    7. values.add(value);
    8. }
    9. }
    10. // 数据校验
    11. @Override
    12. public boolean isValid(Integer value, ConstraintValidatorContext context) {
    13. return values.contains(value);
    14. }
    15. }

     编写完校验器后,将检验注解和校验器关联

     最后,我们就可以在想要校验的字段上使用注解进行校验规则约束了。使用如下:

    1. @Getter
    2. @Setter
    3. @TableName("pms_brand")
    4. @ApiModel(description = "品牌表")
    5. public class PmsBrand implements Serializable {
    6. private static final long serialVersionUID = -21640018136618420L;
    7. @NotNull(message = "修改时id必须指定",groups = {Update.class})
    8. @Null(message = "新增时id不能指定,因为id自增",groups = {Insert.class})
    9. @TableId(value = "brand_id", type = IdType.AUTO)
    10. @ApiModelProperty("品牌id")
    11. private Long brandId;
    12. @NotBlank(message = "品牌名不能为空",groups = {Insert.class, Update.class})
    13. @ApiModelProperty("品牌名")
    14. private String name;
    15. @URL(message = "需要是一个合法的URL",groups = {Insert.class, Update.class})
    16. @NotBlank(message = "logo地址不能为空")
    17. @ApiModelProperty("品牌logo")
    18. private String logo;
    19. @ListValue(value = {0,1},groups = {Insert.class})
    20. @ApiModelProperty("0:显示、1:隐藏")
    21. private Integer showStatus;
    22. }

    效果如下:

    总结

    哈哈哈,继续加油吧!少年

  • 相关阅读:
    C语言——三种方式实现学生信息管理
    HTML5的新特性有哪些?
    从零开始学Spring Boot系列-前言
    垃圾回收器-G1垃圾回收器详解
    微信小程序overflow-x超出部分样式不渲染
    Pull down实验步骤
    携职教育:2022下半年系统集成项目管理工程师备考常见问题
    倍福TwinCAT3实现CSV、TXT文件读写操作
    Linux系统的FTP服务
    变电站运维服务方案
  • 原文地址:https://blog.csdn.net/python15397/article/details/126615263
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号