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
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(); }
校验错误后会有默认的响应
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(); }
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格式:
3、分组校验:
给校验注解标注什么时候需要校验
试想一下,新增和更新时,对实体的不同字段的校验是不同的,例如在新增时主键必须为空,但是修改时,主键必须不为空,此时就必须要用到分组
校验
1)声明分组,实际上是接口
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)测试
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)测试