Spring 参数校验及通用异常信息返回

系列文章目录


叙述
用注解 @Validated、@Valid 进行参数验证,相对于以前常用的 if 等条件,会显得简练很多,而且显得更加优雅。

然后是他俩的区别

@Validated:用在方法入参上无法单独提供嵌套验证功能,不能用在成员属性(字段)上,也无法提示框架进行嵌套验证,能配合嵌套验证注解 @Valid 进行嵌套验证。

@Valid:用在方法入参上无法单独提供嵌套验证功能,能够用在成员属性(字段)上,提示验证框架进行嵌套验证,能配合嵌套验证注解 @Valid 进行嵌套验证。

一、Spring 参数校验及通用异常信息返回

1.安装 Maven 依赖

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

2.常用的验证注解

@Null  被注释的元素必须为null
@NotNull  CharSequence, Collection, Map 和 Array 对象不能是 null, 但可以是空集(size = 0)。  
@NotEmpty  CharSequence, Collection, Map 和 Array 对象不能是 null 并且相关对象的 size 大于 0。  
@NotBlank  String 不是 null 且去除两端空白字符后的长度(trimmed length)大于 0。 
@AssertTrue  被注释的元素必须为true
@AssertFalse  被注释的元素必须为false
@Min(value)  被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)  被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)  被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)  被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max,min)  被注释的元素的大小必须在指定的范围内。
@Digits(integer,fraction)  被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past  被注释的元素必须是一个过去的日期
@Future  被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式。
@Email 被注释的元素必须是电子邮件地址
@Length 被注释的字符串的大小必须在指定的范围内
@Range  被注释的元素必须在合适的范围内

3.全局异常捕获

ControllerAdvice 用于捕获全局的控制器抛出的异常,这里只列出了验证相关的异常,其他 Exception 或者自定义的异常,都可以在这里捕获。

@RestControllerAdvice
@Slf4j
@Component
public class GlobalExceptionHandler {
   /**
     * 处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常
     *
     * @param e e
     * @return Result
     */
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public String bindExceptionHandler(BindException e) {
        log.error("{}, 发生异常: {}", httpServletRequest.getRequestURI(), e);
        String message = e.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return Result.build().errorJson(message);
    }
 
    /**
     * 处理请求参数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常。
     *
     * @param httpServletRequest httpServletRequest
     * @param e                  e
     * @return Result
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public String exceptionHandler(HttpServletRequest httpServletRequest, MethodArgumentNotValidException e) {
        log.error("{}, 发生异常: {}", httpServletRequest.getRequestURI(), e);
        String errField = Objects.nonNull(e.getBindingResult().getFieldError()) ? ":" + e.getBindingResult().getFieldError().getField() : "";
        Object rejectValue = e.getBindingResult().getFieldError().getRejectedValue();
        return Result.build().errorJson(StrUtil.format("字段{}不允许值:{}", errField, rejectValue));
    }
 
    /**
     * 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
     *
     * @param httpServletRequest httpServletRequest
     * @param e                  e
     * @return Result
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public String exceptionHandler(HttpServletRequest httpServletRequest, ConstraintViolationException e) {
        log.error("{}, 发生异常: {}", httpServletRequest.getRequestURI(), e);
        StringBuilder sb = new StringBuilder();
        e.getConstraintViolations().forEach(constraintViolation -> {
            sb.append(StrUtil.format("字段:{},不允许值:{},{} ", constraintViolation.getPropertyPath(),
                    constraintViolation.getInvalidValue(), constraintViolation.getMessage()));
        });
        return Result.build().errorJson(sb.toString().trim());
    }
 
}

4.如何使用

比如新建这样一个类,给对应的字段添加注解以及设置 message 参数,如果不设置 message, 会使用默认的错误消息

public class User {
    @NotEmpty(message = "请输入用户名")
    private String username;
    @NotEmpty
    @Length(min=6,max=18,message = "密码在6位到18位之间")
    private String password;
}

如果验证不符合会自动抛出异常,由上面的 exceptionHandler 进行捕获处理。

针对方法的普通变量参数进行验证时,@Validated 需要加在控制器上,然后变量前加验证注解

这个如果自动抛出异常的话时 ConstraintViolationException

@RestController
@RequestMapping("/file")
@Validated
public class FileController{    
   @DeleteMapping("/deleteFile")
    public Result<?> deleteFile( @NotBlank String jobId,@NotBlank String filePath) throws IOException {
        return jobService.deleteFile(jobId, filePath);
    }
}
{
    "code": "1",
    "data": {},
    "msg": "操作失败:字段:deleteFile.jobId,不允许值:null,不能为空 字段:deleteFile.filePath,不允许值:null,不能为空"
}

用 Validated 或 Valid 注解,验证一个实体类,也就是接收 json 数据,这个需要将注解标注于变量前面

这个如果自动抛出异常的话时 MethodArgumentNotValidException

    @PostMapping
    public Result<?> add(@RequestBody @Validated JobDO jobDO) {
        return Result.build(jobService.insertOrUpdate(jobDO)).success();
    }
 
    @PostMapping
    public Result<?> add(@RequestBody @Valid JobDO jobDO) {
        return Result.build(jobService.insertOrUpdate(jobDO)).success();
    }
{
    "code": "1",
    "data": {},
    "msg": "操作失败:字段:fileId,不允许值:null,不能为空"
}

5.嵌套校验

Validation 的分组验证,比如添加 / 编辑数据时,添加是不需要验证 id 的,而编辑的话 id 是必须的,还有可能这个类被不同控制器,方法使用时需要的参数不同,这时可以指定其 groups 参数指定要验证的规则。

    //用于声明验证规则所属的分组 
    public interface EditSelectListGroup
    {
    }
    
    public class SelectList {
        //指定验证规则属于哪个分组
        @NotNull(message = "id不能为空",groups = {EditSelectListGroup.class})
        @Min(value = 1, message = "id必须为正整数" ,groups= {EditSelectListGroup.class})
        private Long id;
 
        @NotNull(message = "props不能为空")
        @Size(min = 1, message = "至少要有一个属性")
        private List<Item> props;
    }
    //指定使用EditSelectListGroup分组下的验证规则,如果不指定分组,那么SelectList的字段id上注解@NotNull和@Min会被忽略
    //此处必须使用@Validated而不是@Valid,因为@Valid不支持分组条件
    @PutMapping
    public Result<?> edit(@RequestBody @Validated(value = {EditSelectListGroup.class}) JobDO jobDO) {
        //xxxxxxxxxxxxxxxxxxxxxxx
    }

6.数据传递到 spring 中的执行过程

前端通过 HTTP 协议将数据传递到 Spring,Spring 通过 HttpMessageConverter 类将流数据转换成 Map 类型,然后通过 ModelAttributeMethodProcessor 类对参数进行绑定到方法对象中,并对带有 @Valid 或 @Validated 注解的参数进行参数校验,对参数进行处理和校验的方法为 ModelAttributeMethodProcessor.resolveArgument (…),通过查看源码,当 BindingResult 中存在错误信息时,会抛出 BindException 异常,BindException 实现了 BindingResult 接口 (BindResult 是绑定结果的通用接口, BindResult 继承于 Errors 接口),所以该异常类拥有 BindingResult 所有的相关信息,因此我们可以通过捕获该异常类,对其错误结果进行分析和处理。这样,我们对是 Content-Type 为 application/x-www-form-urlencoded 的请求 (也就是表单),的参数校验的异常处理就解决了。

对于不同的传输数据的格式 spring 采用不同的 HttpMessageConverter(http 参数转换器)来进行处理,比如 JasksonHttpMessageConverter, 或者使用 fastjson 的话,可以自定义 FastJsonHttpMessageConverter

HttpMessageConverter 简介
HTTP 请求和响应的传输是字节流,意味着浏览器和服务器通过字节流进行通信。但是使用 Spring MVC 的 Controller 类中的方法都是返回 String 类型或其他 Java 对象,如何将对象转换成字节流进行传输?这时就需要一个消息转化器。

在报文到达 Spring MVC 和从 Spring MVC 出去,都存在一个字节流到 Java 对象的转换问题。在 Spring MVC 中,它是由 HttpMessageConverter 来处理的。

当请求报文来到 java 中,它会被封装成为一个 ServletInputStream 的输入流,供我们读取报文。响应报文则是通过一个 ServletOutputStream 的输出流,来输出响应报文。

针对不同的数据格式,Spring MVC 会采用不同的消息转换器进行处理,当使用 json 作为传输格式时,Spring MVC 会采用 MappingJacksonHttpMessageConverter 消息转换器, 而且底层在对参数进行校验错误时,抛出的是 MethodArgumentNotValidException 异常,因此我们需要对 BindException 和 MethodArgumentNotValidException 进行统一异常管理,最终代码演示如上所示。

二、日期date是string类型

1.需求背景

     有一个需求,在前端传过来的时间格式的字符串进行校验,是否符合"yyyy-MM-dd HH:mm:ss",在SpringBoot中当然可以用@Datetimeformat注解来进行验证,但字段的属性得用Date类型,由于我的项目中该字段用了String类型,需要对类型进行转换不太符合要求,所有用到了@Pattern注解。

   在实体类的字段名上添加@Pattern注解,有个属性regexp,该属性的值就是正则表达式。

   "yyyy-MM-dd HH:mm:ss"的正则表达式如下:
"^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$"

我们还需用到@Validated注解,该注解使用在controller层的方法参数中,只有使用该注解,@pattern中的时间格式校验才起作用,这一点尤其重要。

2. 实现案例

     entity:
//删除时间
    @Pattern(regexp = "^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$")
    private String deletedAt;

controller:

 /**
     * 添加设备类别
     */
    @ApiOperation(value = "添加设备类别",notes="传入参数是category对象")
    @PostMapping("/category")
    public int addCategory(@Validated Category category){
            return iCategory.addCategory(category);
    }

测试结果:

   当输入的时间为2020-08-26或2020/08/26 11:22:33,时间格式不符合"yyyy-MM-dd HH:mm:ss",就会出现报错,如下所示:

三、java中参数校验的运用和常用的几种校验注解

java中参数校验的运用和常用的几种校验注解

总结

提示:这里对文章进行总结:

例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值