JSR303检验的快速入门(基本使用,分组效验、自定义注解效验)

89 阅读4分钟

使用步骤

1.依赖

<!--数据校验-->
 <dependency>
     <groupId>javax.validation</groupId>
     <artifactId>validation-api</artifactId>
     <version>2.0.1.Final</version>
 </dependency>

2.在entity类的属性上添加注解(根据需求添加)

注解详细信息
@Null被注释的元素必须为 null
@NotNull被注释的元素必须不为 null
@AssertTrue被注释的元素必须为 true
@AssertFalse被注释的元素必须为 false
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)被注释的元素的大小必须在指定的范围内
@Digits(integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(value)被注释的元素必须符合指定的正则表达式

ps:

  1. @NotNull只要不为空,校验任意类型;
  2. @NotBlank至少有一个非空字符,校验字符;
  3. @NotEmpty非空,也不能内容为空,校验字符,集合,数组。

3.开启校验功能:在controller类的方法的参数上加上@Valid 属性

4.校验失败的处理:

  • 第一种:单独处理
public R save(@Valid  @RequestBody BrandEntity brand,BindingResult result){
        if(result.hasErrors()){
            Map<String,String> map = new HashMap<>();
            //1、获取校验的错误结果
            result.getFieldErrors().forEach((item)->{
                //FieldError 获取到错误提示
                String message = item.getDefaultMessage();
                //获取错误的属性的名字
                String field = item.getField();
                map.put(field,message);
            });
            return R.error(400,"提交的数据不合法").put("data",map);
        }else {
			brandService.save(brand);
        }
        return R.ok();
    }
  • 第二种,抛出异常后统一处理(推荐)
  1. 定义@RestControllerAdvice处理请求异常类
  2. 将@ExceptionHandler(value= xxx.class)注解根据异常类型标注在方法上,编写处理逻辑
@Slf4j
@RestControllerAdvice
public class ExceptionControllerAdvice {

    @ExceptionHandler(value= MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
        log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        Map<String,String> errorMap = new HashMap<>();
        bindingResult.getFieldErrors().forEach((error)->{
            //存储校验字段名,以及校验字段失败提示
            errorMap.put(error.getField(),error.getDefaultMessage());
        });
        return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
    }

    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){

        log.error("错误:",throwable);
        return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
    }
}
  1. 定义异常枚举类
public enum BizCodeEnume {
    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败");
    private int code;
    private String msg;
    BizCodeEnume(int code,String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

分组校验

场景:在不同情况下,对同一个类同一个字段的要求是不一样的,举例而言,这里有一个类User,在初始添加(add)用户信息的时候,姓名不可以空,但是如果只是修改(update)其他的User信息的时候,姓名为空,也是可以的。

步骤:

  1. 在校验注解上加上groups = {xxx.class, ...}属性,值可以是任意interface接口,例如@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class});
  2. 在开启校验处,将@Valid注解改为@Validated({xxx.class}),例如@Validated({AddGroup.class})就表示只校验该组的属性;注意:未添加任何分组的校验将会无效,开启校验的时候,如果添加了分组信息,那么只会校验同样页添加了该分组的属性。

自定义校验

虽然提供功能,已经能够满足大部分场景,但是对于特殊的情况,需要通过自定义的注解完成,比如说只能用0、1代表性别,开关与否等等。

1)、编写一个自定义的校验注解

@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    String message() default "{com.lx.common.valid.ListValue.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    int[] vals() default { };
}

2)、编写配置文件ValidationMessages.properties,给自定义的校验配置校验失败的信息。

com.lx.common.valid.ListValue.message=显示状态只能为10

3)、编写一个自定义的校验器 ConstraintValidator

实现ConstraintValidator接口,第一个参数为绑定的校验注解名,第二个参数为校验的属性类型,完成初始化与判断方法。

public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    private Set<Integer> set = new HashSet<>();
    /**
     * @Description: 根据注解中的属性初始化
     * @Param0: constraintAnnotation
     **/
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val:vals) {
            set.add(val);
        }
    }

    /**
     * @Description: 判断校验是否成功
     * @Param0: value 被校验值
     * @Param1: context
     **/
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}

4)、关联自定义的校验器和自定义的校验注解

@Constraint(validatedBy = { ListValueConstraintValidator.class })

5)、使用

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

完整代码

controller

/**
 * 保存
 */
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
    brandService.save(brand);

    return R.ok();
}

/**
 * 修改
 */
@RequestMapping("/update")
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
    brandService.updateById(brand);

    return R.ok();
}

entity

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
	@Null(message = "新增不能指定id",groups = {AddGroup.class})
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
	private String name;
	/**
	 * 品牌logo地址
	 */
	@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	@NotNull(groups = {AddGroup.class, UpdateGroup.class})
	@ListValue(vals = {0,1},groups = {AddGroup.class,UpdateGroup.class})
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotEmpty(groups={AddGroup.class})
	@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull(groups={AddGroup.class})
	@Min(value = 0,message = "排序必须大于等于0",groups={AddGroup.class,UpdateGroup.class})
	private Integer sort;

}

测试1:

测试2: