@Valid注解使用说明及统一异常处理演示

目录

@Valid注解

案例说明

之前的做法

现在的做法

总结


@Valid注解

案例说明

先来看一下数据库表,发现标签名称和操作者id都允许为空,其实这里正常来讲应该设置为非空的。

@Valid注解使用说明及统一异常处理演示_第1张图片

tips:当数据库表设置为非空的时候,如果我们没有添加相应的非空字段,就不会添加成功!

但是这里,数据库中没有进行非空约束。于是,我们尝试添加为空的数据,肯定是会添加成功的。

@PostMapping("insert")
@ApiOperation(value = "新增行业标签",notes = "新增行业标签,这里是详细描述信息!",httpMethod = "POST")
public Result insert(Industry industry){
    // todo 需要先校验对象
    industryService.save(industry);
    return Result.success();
}

 @Valid注解使用说明及统一异常处理演示_第2张图片

 @Valid注解使用说明及统一异常处理演示_第3张图片

 

但是这是我们不希望看到的,我们希望看到的是name和operator_id字段都有值。所以我们需要对入参进行校验。

其实,不管数据库有没有设置为空,我们都要进行入参的校验工作,加强保证数据的合理性。

之前的做法

校验请求参数,我们之前的一般做法都是:

获取到当前参数,然后进行判断,如果不满足条件,我们返回错误提示。

@PostMapping("insert")
@ApiOperation(value = "新增行业标签",notes = "新增行业标签,这里是详细描述信息!",httpMethod = "POST")
public Result insert(Industry industry){
​
    String name = industry.getName();
    Integer operatorId = industry.getOperatorId();
    if (StringUtils.isEmpty(name)){
        return Result.fail("标签名称不能为空");
    }
    if (operatorId == null){
        return Result.fail("创建人不能为空");
    }
    
    industryService.save(industry);
    return Result.success();
}

@Valid注解使用说明及统一异常处理演示_第4张图片

 @Valid注解使用说明及统一异常处理演示_第5张图片

 @Valid注解使用说明及统一异常处理演示_第6张图片

 @Valid注解使用说明及统一异常处理演示_第7张图片

 @Valid注解使用说明及统一异常处理演示_第8张图片

@Valid注解使用说明及统一异常处理演示_第9张图片

@Valid注解使用说明及统一异常处理演示_第10张图片

@Valid注解使用说明及统一异常处理演示_第11张图片

现在的做法

只需要在对应的形参添加@Valid注解,然后在对应的实体类属性添加对应约束的注解即可。

@PostMapping("insert")
@ApiOperation(value = "新增行业标签",notes = "新增行业标签,这里是详细描述信息!",httpMethod = "POST")
public Result insert(@Valid Industry industry){
    industryService.save(industry);
    return Result.success();
}
@ApiModelProperty(value = "标签名称")
@TableField("name")
@NotBlank(message = "标签名称不能为空")
private String name;
​
@ApiModelProperty("操作用户id")
@TableField("operator_id")
@NotNull(message = "创建人不能为空")
private Integer operatorId;

此时,我们测试一下

@Valid注解使用说明及统一异常处理演示_第12张图片

@Valid注解使用说明及统一异常处理演示_第13张图片

 我们发现,也是无法添加成功的。直接报异常了~

我们查看一下,控制台打印的信息:

2022-10-01 12:39:19.285  WARN 7736 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'industry' on field 'operatorId': rejected value [null]; codes [NotNull.industry.operatorId,NotNull.operatorId,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [industry.operatorId,operatorId]; arguments []; default message [operatorId]]; default message [创建人不能为空]]

但是我们希望按照之前一样的统一返回结果的格式进行返回。

我们可以做一个统一异常处理!

@ControllerAdvice
public class GlobalException {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result error(Exception e){
        e.printStackTrace();
        return Result.fail("执行了全局异常处理");
    }
}

再执行发现返回的结构一致了。

但是返回的消息msg,并不是我们@NotNull注解中的message内容

@Valid注解使用说明及统一异常处理演示_第14张图片

 此时捕获的是全局异常类,那么针对这种特定的具体异常还可以进行单独处理!

所以我们需要针对特定异常,来单独处理!

我们通过控制台,可以看到异常的名称叫做BindException,那么我们就针对改异常进行处理~

@ControllerAdvice
public class GlobalException {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result error(Exception e){
        e.printStackTrace();
        return Result.fail("执行了全局异常处理");
    }
​
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public Result error(BindException e){
        e.printStackTrace();
        return Result.fail("特定异常被执行");
    }
​
}

@Valid注解使用说明及统一异常处理演示_第15张图片

现在,如何能够获取到@NotNull注解中的message值返回呢?

@ExceptionHandler(BindException.class)
@ResponseBody
public Result error(BindException e){
    e.printStackTrace();
    String defaultMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
    return Result.fail(defaultMessage);
}

这样就可以了。

@Valid注解使用说明及统一异常处理演示_第16张图片

解析一下:

BindException这个参数校验异常,会把所有的异常结果放到BindingResult接口中。我们可以通过当前异常调用它内部方法,拿到它当前的所有异常结果,然后获取到第一条异常的message消息。

网上很多人的做法:

网上很多教程都是把BindingResult放在接口的参数中,然后在接口里面处理。

例如:

@RequestMapping("/save")
    public R save(@RequestBody @Valid BrandEntity brand, BindingResult result){
        if (result.hasErrors()){
            Map 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();
        }
}

测试:

{
    "msg": "提交的数据不合法",
    "code": 400,
    "data": {
        "name": "品牌名不能为空,或者空格,或者空串",
        "logo": "品牌logo必须是一个合法的url",
        "sort": "排序字段必须大于等于0"
    }
}

其实这样不是很好,但凡有校验的,每个接口都要传BindingResult,还要写处理逻辑。

不过我们可以参考一下,进行更改。

    @ExceptionHandler(BindException.class)
    @ResponseBody
    public Result error(BindException e){
//        e.printStackTrace();
//        String defaultMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        BindingResult result = e.getBindingResult();
        Map map = new HashMap<>();
        //1.获取校验的错误结果
        result.getFieldErrors().forEach(item -> {
            // FieldError 获取到错误提示
            String message = item.getDefaultMessage();
            // 获取错误的属性的名字
            String field = item.getField();
            map.put(field,message);
        });
        return Result.fail("参数类型异常",map);
    }
{
    "code": 500,
    "msg": "参数类型异常",
    "data": {
        "name": "标签名称不能为空",
        "operatorId": "创建人不能为空"
    }
}

总结

@Valid注解主要用作就是参数校验

对于简单的校验,省去了我们每次还得写大量重复代码的工作。

步骤:

  1. 添加依赖

  2. 添加注解@Valid到对应的控制器方法入参

  3. 在对应的入参实体类属性上添加对应的功能注解,比如@NotNull,其中message属性添加不匹配时的提示信息。

  4. 一旦不匹配会抛出异常,需设置统一异常处理。针对该参数校验的异常添加统一异常处理。

tips:

javax.validation 是基于JSR-303标准开发出来的,使用注解方式实现,及其方便,但是这只是一个接口,没有具体实现。

Hibernate-Validator是一个hibernate独立的包,可以直接引用,他实现了javax.validation同时有做了扩展,比较强大。

所以我们引入依赖需要这两个:



    javax.validation
    validation-api
    1.1.0.Final


    org.hibernate
    hibernate-validator
    5.2.4.Final

当然,Spring有对该依赖进行整合,所以我们可以直接引入如下这个依赖即可。



    org.springframework.boot
    spring-boot-starter-validation
    2.7.3

 常用注解:

限制 说明
@Null 用在基本类型上;限制只能为null
@NotNull 用在基本类型上;不能为null,但可以为empty
@NotEmpty 用在集合类上面;不能为null,而且长度必须大于0 
@NotBlank

用在String上面;只能作用在String上,不能为null,而且调用trim()后,长度必须大于0 

ps:意味着不能为null,不能为空串,还不能是只有空格的

@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@AssertFalse 限制必须为false
@AssertTrue

限制必须为true

@Pattern 自定义规则,可以通过regexp属性传入正则表达式,例如regexp = "/^[a-zA-Z]$/",意思是要求只传一个字母
@Url 这个注解是org.hibernate.validator.constraints下的,也可以使用,限制必须是一个合法的url地址

统一异常处理:

@Slf4j
@ControllerAdvice
public class GulimallException {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public R handleValidException(MethodArgumentNotValidException e){
        log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        Map errorMap = new HashMap<>();
        bindingResult.getFieldErrors().forEach((fieldError)->{
            errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
        });
        return R.error(400,"数据校验出现问题").put("data",errorMap);
    }

    @ExceptionHandler(Throwable.class)
    @ResponseBody
    public R handleException(Throwable throwable){
        return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.UNKNOW_EXCEPTION.getValue());
    }
}

你可能感兴趣的:(java,spring,boot)