IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringBoot项目,Java Bean数据校验-JSR -> 正文阅读

[Java知识库]SpringBoot项目,Java Bean数据校验-JSR

Bean Validation 与 JSR 的关系

参考资料: https://jcp.org/en/jsr/summary?id=bean%20validation
在这里插入图片描述

JSR380Bean Validation 2.0
JSR349Bean Validation 1.1
JSR303Bean Validation 1.0

JSR 规范

  1. javax.validation.api
    从Java 8开始, Java EE 改名为 Jakarta EE, javax.validation下的相关包移动到了jakarta.validation包下。
  2. hibernate-validator

Bean Validation 下注解

参考 https://blog.csdn.net/dear_little_bear/article/details/104556183

  1. 两个标识注解

    注解功能
    @Valid标记用于验证级联的属性、方法参数或方法返回类型
    @Validatedspring 提供的扩展注解,方便的用于分组校验
  2. 22个约束注解
    在这里插入图片描述

    注解功能
    @AssertFalse检查元素是否为 false,支持数据类型:boolean
    @AssertTrue检查元素是否为 true,支持数据类型:boolean
    @DecimalMax(value=, inclusive=)inclusive:boolean,默认 true,表示包含或等于
    value:当 inclusive=false 时,检查带注解的值是否小于指定的最大值。
    当 inclusive=true 检查该值是否小于或等于指定的最大值。
    @DecimalMin(value=, inclusive=)与 @DecimalMax 注解功能相反
    @Digits(integer=, fraction=)检查值是否为最多包含 integer 位整数和 fraction 位小数的数字
    @Email检查指定的字符序列是否为有效的电子邮件地址
    @Max(value=)检查值是否小于或等于指定的最大值
    @Min(value=)检查值是否大于或等于指定的最大值
    @NotBlank检查字符序列是否为空,以及去空格后的长度是否大于 0。
    与 @NotEmpty 的不同之处在于,此约束只能应用于字符序列。
    支持数据类型:CharSequence
    @NotNull检查值是否不为 null
    支持数据类型:任何类型
    @Null检查值是否为 null
    @NotEmpty检查元素是否为 null 或 空
    支持数据类型:CharSequence, Collection, Map, arrays
    @Size(min=, max=)检查元素个数是否在 min(含)和 max(含)之间
    支持数据类型:CharSequence,Collection,Map, arrays
    @Negative检查元素是否严格为负数。零值被认为无效。
    @NegativeOrZero检查元素是否为负或零
    @Positive检查元素是否严格为正。零值被视为无效
    @PositiveOrZero检查元素是否为正或零。
    @Future检查日期是否在未来
    @FutureOrPresent检查日期是现在或将来
    @Past检查日期是否在过去
    @PastOrPresen检查日期是否在过去或现在
    @Pattern(regex=, flags=)根据给定的 flag 匹配,检查字符串是否与正则表达式 regex 匹配
  3. Hibernate Validator 附加的注解
    在这里插入图片描述

    约束功能
    @URL(protocol=,host=,port=,regexp=,flags=)被注释的字符串必须是一个有效的 URL
    @Range(min=, max=)元素必须在合适的范围内
    @Length(min=, max=)字符串的大小必须在指定的范围内

Spring Boot 项目中使用 Bean Validation

基本校验

  1. 引入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
        <version>2.5.2</version>
    </dependency>
    
  2. 实例类上标注校验注解,如@NotBlank

    @Data
    @TableName("pms_brand")
    public class BrandEntity implements Serializable {
    	private static final long serialVersionUID = 1L;
    	@TableId
    	private Long brandId;
    	@NotBlank
    	private String name;
    	private String logo;
    	private String descript;
    	private Integer showStatus;
    	private String firstLetter;
    	private Integer sort;
    }
    
  3. controller 中,请求的方法参数添加注解@Valid,表明参数需要校验

    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand){ // 表明brand参数是需要校验的,校验规则根据实体类中的定义
    brandService.save(brand);
    
       return R.ok();
    }
    
  4. postman 测试
    在这里插入图片描述
    后端控制台报异常:

    Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.feng.common.utils.R com.feng.mall.product.controller.BrandController.save(com.feng.mall.product.entity.BrandEntity):
    [Field error in object ‘brandEntity’ on field ‘name’: rejected value [];
    codes [NotBlank.brandEntity.name,NotBlank.name,NotBlank.java.lang.String,NotBlank];
    arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [brandEntity.name,name]; arguments []; default message [name]];
    default message [must not be blank]] ]

    默认的异常信息定义在 ValidationMessages.properties配置文件中,如

    javax.validation.constraints.NotBlank.message = must not be blank

  5. 自定义异常提示信息

    	@NotBlank(message = "品牌名必须提交")
    	private String name;
    

    测试时,后台显示的异常信息如下:

    Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.feng.common.utils.R com.feng.mall.product.controller.BrandController.save(com.feng.mall.product.entity.BrandEntity): [Field error in object ‘brandEntity’ on field ‘name’: rejected value []; codes [NotBlank.brandEntity.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [brandEntity.name,name]; arguments []; default message [name]]; default message [品牌名必须提交]] ]

  6. 使用 BindingResult 参数感知校验异常,并封装为自定义格式

    1. BindingResult参数必须紧跟在校验参数的后面,一旦校验出错,可以通过该参数获取到校验出错的信息,可以根据获取到的错误信息,自定义封装返回异常。
    2. 代码:
      @RequestMapping("/save")
      //@RequiresPermissions("product:brand:save")
      public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
          if (result.hasErrors()) {
      
              Map<String, String> map = new HashMap<>();
              // 1. 获取校验的错误结果
              result.getFieldErrors().forEach( item -> {
      
                  // 获取到错误提示消息
                  String message = item.getDefaultMessage();
                  // 获取属性的名字
                  String field = item.getField();
                  map.put(field, message);
      
              });
              return R.error(400, "提交的数据不合法").put("data", map);
          }
      	brandService.save(brand);
      
          return R.ok();
      }
      
    3. postman 测试结果
      在这里插入图片描述

统一异常处理

对于基本校验处,使用 BindingResult 参数,在每个请求方法中处理,会显得很繁琐,并且也不利于维护,对业务有侵入。因此,引入下面的统一校验,对于出现的异常统一进行处理。

  1. 抽取一个统一异常处理类 MallExceptionControllerAdvice

    @Slf4j
    @ResponseBody
    // 该注解标注的类是用来处理 controller 出现的异常类,Controller的方法中不去感知
    // 出现的异常(BindingResult可以感知到),那么异常就会来到这个类,会得到统一处理
    @ControllerAdvice(basePackages = "com.feng.mall.product.controller")
    // @RestControllerAdvice 想等于 @ResponseBody 和 @ControllerAdvice 的组合
    public class MallExceptionControllerAdvice {
    
        /**
         * @ExceptionHandler 告诉 handleValidException 这个方法能处理
         * MallExceptionControllerAdvice 类获得异常中的 那些异常
         *
         * 如 value = Exception.class, 表明可以感知所有异常
         */
    
        @ExceptionHandler(value = Exception.class)
        public R handleValidException(Exception e) {
            log.error("数据校验出现问题:{},异常类型:{}", e.getMessage(), e.getClass());
            return R.error();
        }
    }
    
  2. 将 controller 代码还原回原来样子

    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand){
    	brandService.save(brand);
        return R.ok();
    }
    
  3. 测试
    在这里插入图片描述
    后台日志:

    数据校验出现问题:Validation failed for argument [0] in public com.feng.common.utils.R com.feng.mall.product.controller.BrandController.save(com.feng.mall.product.entity.BrandEntity) with 2 errors: [Field error in object ‘brandEntity’ on field ‘sort’: rejected value [null]; codes [NotNull.brandEntity.sort,NotNull.sort,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [brandEntity.sort,sort]; arguments []; default message [sort]]; default message [排序字段不能为空]] [Field error in object ‘brandEntity’ on field ‘firstLetter’: rejected value [null]; codes [NotEmpty.brandEntity.firstLetter,NotEmpty.firstLetter,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [brandEntity.firstLetter,firstLetter]; arguments []; default message [firstLetter]]; default message [检索首字母不能为空]] ,异常类型:class org.springframework.web.bind.MethodArgumentNotValidException

  4. 表明统一异常处理类已经生效,并且捕获到了异常。为了能精确捕获异常,我们将统一处理异常的方法中的Exception 改为 MethodArgumentNotValidException,并且获取到BindingResult ,封装异常。

    @Slf4j
    @ResponseBody
    // 该注解标注的类是用来处理 controller 出现的异常类,Controller的方法中不去感知
    // 出现的异常(BindingResult可以感知到),那么异常就会来到这个类,会得到统一处理
    @ControllerAdvice(basePackages = "com.feng.mall.product.controller")
    // @RestControllerAdvice 想等于 @ResponseBody 和 @ControllerAdvice 的组合
    public class MallExceptionControllerAdvice {
    
        /**
         * @ExceptionHandler 告诉 handleValidException 这个方法能处理
         * MallExceptionControllerAdvice 类获得异常中的 那些异常
         *
         * 如 value = Exception.class, 表明可以感知所有异常
         */
    
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public R handleValidException(MethodArgumentNotValidException e) {
            log.error("数据校验出现问题:{},异常类型:{}", e.getMessage(), e.getClass());
            BindingResult result = e.getBindingResult();
            if (result.hasErrors()) {
                Map<String, String> map = new HashMap<>();
                // 1. 获取校验的错误结果
                result.getFieldErrors().forEach( item -> {
    
                    // 获取到错误提示消息
                    String message = item.getDefaultMessage();
                    // 获取属性的名字
                    String field = item.getField();
                    map.put(field, message);
                });
                return R.error(400, "提交的数据不合法").put("data", map);
            }
            return R.error();
        }
    }
    
  5. 再次测试,通过统一异常处理类,得到了我们想要的结果。(校验失败的结果)
    在这里插入图片描述

分组校验

想象一种场景:我们新增一条 BrandEntity 数据 和 修改一条 BrandEntity 数据,校验的字段可能是不一样的。比如主键字段,新增的时候,不要求填写,而修改的时候,就必须带有主键字段。因此,我们可以使用分组校验功能。根据不同情况,使用不同的校验规则。

  1. 如果要使用分组校验规则,需要给实体类的字段上,标注分组校验参数groups
    1. 添加分组,对于指定的组,只是一个空接口。如下有三个组,分别触发三种情况的校验:

      public interface AddGroup {
      }
      
      public interface UpdateGroup {
      }
      
      public interface UpdateStatusGroup {
      }
      
    2. 实体类字段上,标注分组,如:BrandEntity

      @Data
      @TableName("pms_brand")
      public class BrandEntity implements Serializable {
      	private static final long serialVersionUID = 1L;
      
      	@NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class, UpdateStatusGroup.class})
      	@Null(message = "新增不能制定品牌id", groups = AddGroup.class)
      	@TableId
      	private Long brandId;
      
      	@NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})
      	private String name;
      	
      	@NotEmpty(message = "url地址不能为空")
      	@URL(message = "logo必须是一个合法的url地址")
      	private String logo;
      	
      	private String descript;
      
      	private Integer showStatus;
      
      	@NotEmpty(message = "检索首字母不能为空")
      	@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")
      	private String firstLetter;
      
      	@NotNull(message = "排序字段不能为空")
      	@Min(value = 0, message = "排序字段的值必须大于等于0")
      	private Integer sort;
      
      }
      

      如 brandId 字段,@NotNull 触发的情况是在满足 UpdateGroup 和 UpdateStatusGroup 情况下;而 @Null 触发校验,是在 AddGroup 这种情况下。没有标注 group属性的注解,在分组校验的情况下,不生效。

    3. 具体如何触发校验,用到了 @Validated 注解,该注解同@Valid注解一样可以标注在方法参数上,但是该注解可以指定分组,表明校验的时机。
      ```java
      @RequestMapping("/save")
      public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){

       brandService.save(brand);
       
           return R.ok();
       }
       ```
       参数 BrandEntity 会在新增数据的时候进行校验
      
    4. postman测试,分组校验规则生效
      在这里插入图片描述

自定义校验注解

当普通的校验规则不能满足业务需求时候,这个就需要自定义校验规则了。

  1. 编写自定义校验注解
    校验注解,必须满足规范

    	// 当校验出错时,默认的提示信息去哪里取,默认的错误信息,应该放在 ValidationMessages.properties 配置文件中
    	String message() default "{javax.validation.constraints.NotBlank.message}";
    	// 需要支持分组校验功能
    	Class<?>[] groups() default { };
    	// 校验注解还需要支持一些负载信息
    	Class<? extends Payload>[] payload() default { };
    
    @Documented
    @Constraint(validatedBy = { }) // 这里可以指定校验注解使用的校验器,如果这里不指定,需要在初始化的时候指定。
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) // 校验注解可以作用的地方
    @Retention(RUNTIME) // 校验注解触发的时机
    

    自定义校验注解

    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import static java.lang.annotation.ElementType.*;
    import static java.lang.annotation.ElementType.TYPE_USE;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    @Documented
    @Constraint(validatedBy = { ListValueConstraintValidator.class }) // 指定了自定义的校验器
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    public @interface ListValue {
        String message() default "{com.feng.common.valid.ListValue.message}";
    
        Class<?>[] groups() default { };
    
        Class<? extends Payload>[] payload() default { };
    
        int[] vals() default {};
    }
    

    出错后,message 的提示信息,放在了 ValidationMessages.properties 配置文件中,如

    com.feng.common.valid.ListValue.message=\u5fc5\u987b\u63d0\u4ea4\u6307\u5b9a\u7684\u503c
    

    文件中定义的提示信息,使用了unicode码,实际中文是"必须提交指定的值",因为直接写中文,给出的提示信息乱码。没明白为啥不能直接写中文。希望知道的告诉我一声。谢谢!

  2. 编写自定义的校验器

    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import java.util.HashSet;
    import java.util.Set;
    
    public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
    
        private Set<Integer> set = new HashSet<>();
        
        /**
         * 初始化方法,可以拿到注解ListValue的详细信息
         * @param constraintAnnotation
         */
        @Override
        public void initialize(ListValue constraintAnnotation) {
            ConstraintValidator.super.initialize(constraintAnnotation);
    
            int[] vals = constraintAnnotation.vals();
            if (vals.length > 0) {
                for (int val : vals) {
                    set.add(val);
                }
            }
        }
    
        /**
         * 判断是否校验成功
         * @param value 表示提交过来的需要校验的值
         * @param context
         * @return
         */
        @Override
        public boolean isValid(Integer value, ConstraintValidatorContext context) {
            return set.contains(value); // 这里进行校验
        }
    }
    
  3. 关联自定义的校验器和校验注解

    // 校验器 和 校验注解产生关系的代码
    @Constraint(validatedBy = { ListValueConstraintValidator.class }) 
    
  4. 使用自定义的校验注解@ListValue

    @ListValue(vals={0, 1}, groups = { AddGroup.class })
    private Integer showStatus;
    

    vals={0, 1} 里的值,会通过 public void initialize(ListValue constraintAnnotation) 方法中的 constraintAnnotation.vals() 获取到;而 showStatus 的值,会通过 public boolean isValid(Integer value, ConstraintValidatorContext context) 方法中的参数 value获取到,并且在 isValid方法中完成校验。

  5. postman测试
    在这里插入图片描述
    因为 showStatus 字段上标注的注解,限定值只能为 1 和 2,@ListValue(vals={0, 1}, groups = { AddGroup.class }),但是我们测试的时候,showSatus字段值,给的是3,校验失败,给出错误提示。

  6. 因为我们编写的校验器,现在只能校验类型是 Integer 的数据,如果我们想校验 Double类型的数据,此时需要我们自定义Double 类型的校验器。如:

    public class ListValueConstraintValidatorDouble implements ConstraintValidator<ListValue, Double> {
      // 省略具体实现
    }
    
  7. 如果该注解 @ListValue 要想校验两种数据类型,我们只需要修改代码,将代码:

    @Constraint(validatedBy = { ListValueConstraintValidator.class }) 
    

    改为

    @Constraint(validatedBy = { ListValueConstraintValidator.class, ListValueConstraintValidatorDouble.class }) 
    

    即可。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-23 10:35:34  更:2021-07-23 10:37:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/18 18:15:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码