先区分几个注解:
- @NotBlank: 只用于String,不能为null且trim()之后的size>0.
- @NotNull: 不能为 null,但是可以为empty,没有size的约束.
- @NotEmpty:用于集合类、String类不能为null,且长度>0.但是带有空格的字符串校验不出来(空格字符串有长度).
JSR303
- 给Bean添加校验注解 javax.validation.constraints 并填写自己的message提示
- controller 中接收的对象添加注解@Valid开启校验
- 效果:校验错误以后会有默认的响应
/**
* 品牌名
*/
@NotBlank(message = "品牌名为必填项")
private String name;
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult bindingResult){
if (bindingResult.hasErrors()){
Map<String,String> errorsMap = new HashMap<>();
bindingResult.getFieldErrors().forEach(item ->{
errorsMap.put(item.getField(),item.getDefaultMessage());
});
return R.error(400,"提交的数据不合规范.").put("errorMap",errorsMap);
}else{
brandService.save(brand);
}
return R.ok();
}
默认校验的提示信息存放在 ValidationMessages.properties
这个配置文件里面,
校验之后默认的响应:
{
"timestamp": "2020-10-04 10:06:57",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.brandEntity.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"brandEntity.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "不能为空",
"objectName": "brandEntity",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='brandEntity'. Error count: 1",
"path": "/product/brand/save"
}
这种提示不友好,所以我们可以 用 BindingResult 对象进行封装一下,封装之后请求结果如下:
{
"msg": "提交的数据不合规范.",
"errorMap": {
"name": "品牌名为必填项"
},
"code": 400
}
总结:
- 给Bean添加校验注解,并定义自己的提示信息.
- 开启校验功能 @Valid,校验错误之后会有默认的响应.
- 给校验的bean后紧跟一个BindingResult ,就可以获取到校验结果.
统一的异常处理
使用@ControllerAdvance注解,做统一异常处理好处是很多业务的新增修改都会有校验功能,做了统一处理之后,降低代码的冗余.
@Slf4j
//@ResponseBody
//@ControllerAdvice(value = "com.solo.gulimall.product.controller")
@RestControllerAdvice(value = "com.solo.product.controller")
public class ExceptionControllerAdvice {
@ExceptionHandler(Exception.class)
public R handleValidException(MethodArgumentNotValidException exception) {
Map<String, String> errorsMap = new HashMap<>();
BindingResult bindingResult = exception.getBindingResult();
bindingResult.getFieldErrors().forEach(item -> {
errorsMap.put(item.getField(), item.getDefaultMessage());
});
log.error("数据校验出现问题{},异常类型{}", exception.getMessage(), exception.getClass());
return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(), BizCodeEnum.VALID_EXCEPTION.getMsg()).put("errorsMap", errorsMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable) {
log.error("未知异常{},异常类型{}",
throwable.getMessage(),
throwable.getClass());
return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),
BizCodeEnum.UNKNOW_EXEPTION.getMsg());
}
}
分组校验功能(多场景校验)
场景:如果新增和修改两个接口需要验证的字段不同,比如Id字段,新增是不传递,但是修改是必须的,所以可以用分组校验功能来轻松实现。
/**
* 品牌id
* 修改是不可以为null
* 添加是为空
*/
@Null(message = "添加时不能指定id",groups = AddGroup.class)
@NotNull(message = "修改时必须指定品牌id",groups = UpdateGroup.class)
@TableId
private Long brandId;
自定义 AddGroup、UpdateGroup空接口,同时控制层方法根据方法使用 @Validated(XXX.class) 注解
当使用了分组属性之后,那些没有指定分组的校验就会失效。
自定义校验注解
@ListValue(vals = {0,1}, groups = {AddGroup.class, UpdateGroup.class})
private Integer showStatus;
注解中必须包含三个属性:
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class}) // 校验器
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) // 哪都可以标注
@Retention(RUNTIME)
public @interface ListValue {
/**
* 注解必有的三个属性
* @return
*/
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
// 数组,需要用户自己指定
int[] vals() default {};
}
com.atguigu.common.valid.ListValue.message指的是 配置文件 ValidationMessages.properties 里面所定义的自定义提示消息。
ValidationMessages.properties 前面也说过是默认提示配置文件,现在要在resource下面写自己的校验信息。
自定义校验器ConstraintValidator
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
// 存储所有可能的值
private Set<Integer> set=new HashSet<>();
@Override // 初始化,你可以获取注解上的内容并进行处理
public void initialize(ListValue constraintAnnotation) {
// 获取后端写好的限制 这个vals就是ListValue里的vals,我们写的注解是@ListValue(vals={0,1})
int[] value = constraintAnnotation.vals();
for (int i : value) {
set.add(i);
}
}
@Override // 覆写验证逻辑
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// 看是否在限制的值里
return set.contains(value);
}
}
具体的校验器类 实现了ConstraintValidator 接口
第一个泛型参数是所对应的校验注解类型,第二个是校验对象类型。
public interface ConstraintValidator<A extends Annotation, T> {
/**
* Initializes the validator in preparation for
* {@link #isValid(Object, ConstraintValidatorContext)} calls.
* The constraint annotation for a given constraint declaration
* is passed.
* <p>
* This method is guaranteed to be called before any use of this instance for
* validation.
* <p>
* The default implementation is a no-op.
*
* @param constraintAnnotation annotation instance for a given constraint declaration
*/
default void initialize(A constraintAnnotation) {
}
关联校验器和校验注解
在自定义注解上面 使用 @Constraint 注解。
@Constraint(validatedBy = { ListValueConstraintValidator.class}) // 校验器
一个校验注解可以匹配多个校验器