JSR303校验和全局异常处理

先区分几个注解:

  • @NotBlank: 只用于String,不能为null且trim()之后的size>0.
  • @NotNull: 不能为 null,但是可以为empty,没有size的约束.
  • @NotEmpty:用于集合类、String类不能为null,且长度>0.但是带有空格的字符串校验不出来(空格字符串有长度).

JSR303

  1. 给Bean添加校验注解 javax.validation.constraints 并填写自己的message提示
  2. controller 中接收的对象添加注解@Valid开启校验
  3. 效果:校验错误以后会有默认的响应
/**
	 * 品牌名
	 */
	@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}) // 校验器

一个校验注解可以匹配多个校验器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值