JSR303的使用

1、JSR303是什么?

JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指 :向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。

任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。

JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303

规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint

image


2、JSR303如何使用?

1)给bean添加校验注解 :javax.validation.constraints

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

   @TableId
   private Long brandId;

   @NotBlank(message = "品牌名称不能为空")
   private String name;

   @URL(message = "logo必须是一个合法的url")
   private String logo;

   private String descript;

   private Integer showStatus;
   //自定义校验规则
   @Pattern(regexp = "/^[a-zA-Z]$/",message = "检索首字母必须是一个字母")
   private String firstLetter;

   private Integer sort;

}


2)在congtroller总开启校验 @Valid

@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
      return R.ok();
  }


校验错误后会有默认的响应

clipboard


3)如果不想要默认的响应,希望自己自定义校验异常返回的结果

给校验后的bean后面紧跟一个BindingResult就可以获取到校验的结果

@RequestMapping("/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();
  }

clipboard


4)如果每个controller都需要写这种封装数据校验异常的代码,会造成代码冗余

此时需要进行统一异常处理,并将controller方法中的BindingResult去掉,任由其抛出异常

@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
      return R.ok();
}

添加controller统一异常处理器

@Slf4j
@RestControllerAdvice
public class GulimallExceptionControllerAdvice {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
        log.error("数据校验异常{},异常类型{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        HashMap<String, String> map = new HashMap<>();
        bindingResult.getFieldErrors().forEach((fieldError) -> {
            map.put(fieldError.getField(),fieldError.getDefaultMessage());
        } );
        return R.error(400,"数据校验错误").put("data",map);
    }
}

发送postman请求可以看到,返回的格式是我们自己封装的response格式:

clipboard


3、分组校验:

给校验注解标注什么时候需要校验

试想一下,新增和更新时,对实体的不同字段的校验是不同的,例如在新增时主键必须为空,但是修改时,主键必须不为空,此时就必须要用到分组

校验


1)声明分组,实际上是接口

clipboard


2)实体属性校验上添加分组

注意:默认没有指定分组的校验注解,在分组校验的情况下不生效

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

   @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;

   @NotBlank(message = "logo地址不能为空",groups = {AddGroup.class})
   @URL(message = "logo必须是一个合法的url",groups = {AddGroup.class,UpdateGroup.class})
   private String logo;

   private String descript;

   private Integer showStatus;

   @Pattern(regexp = "/^[a-zA-Z]$/",message = "检索首字母必须是一个字母")
   private String firstLetter;

   private Integer sort;

}


3)controller中添加 @Validated({AddGroup.class})

指定需要校验的分组

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


4)测试

clipboard


4、自定义校验注解

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

找到  @NotNull注解,将其上的元注解copy下来

@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {

    String message() default "{com.atguigu.common.valid.ListValue.message}";

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

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

    int[] vals() default {};
}


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

public class ListValueContraintValidator implements ConstraintValidator<ListValue, Integer> {

    private Set<Integer> set = new HashSet<>();

    //初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for(int val : vals){
            set.add(val);
        }
    }

    //判断校验是否成功  value=> controller接收的值 == 等待被校验的值
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
        return set.contains(value);
    }
}


3)将校验器和注解绑定

@Documented
//表明本校验注解 使用ListValueContraintValidator校验器
@Constraint(validatedBy = {ListValueContraintValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {

    String message() default "{com.atguigu.common.valid.ListValue.message}";

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

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

    int[] vals() default {};
}


4)在实体类上使用该校验注解

@ListValue(vals = {0,1},groups = AddGroup.class)   的意思时,新增时校验showStatus,且showStatus必须是 0 / 1

@ListValue(vals = {0,1},groups = AddGroup.class) //当新增时携带了showStatus字段
private Integer showStatus;


5)测试

clipboard

原文地址:https://www.cnblogs.com/houchen/p/15317905.html