springboot参数校验

1.前言

在项目里面,我们需要对前端传入的参数做一个简单的简单的校验,避免出现脏数据和业务逻辑错误。如果每个接口单独写校验逻辑的话,我们需要在controller层做逻辑判断。参数较少时,还勉强能够接受,如果参数和接口较多,无形中加重了工作量,也多了很多重复代码。所以引入注解式参数校验很有必要。

2.引入方式

2.1引入jar包

本文是基于springboot来实现参数校验,引入方式很简单,在pom中引入spring-boot-starter-validation即可。

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

2.2全局异常捕获

一般情况下,我们使用参数校验都需要返回异常信息,搭配全局异常捕获食用效果最佳。这里我就不介绍异常捕获的工作原理了,简单贴一下代码以供参考。先不需要考虑这的捕获器1、2、3、4是干什么的,下面会介绍作用。

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
	//捕获器1
    @ExceptionHandler(value = {MissingServletRequestParameterException.class})
    public ResponseVO<String> handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
        if (log.isErrorEnabled()) {
            log.error(ex.getMessage(), ex);
        }
        return ResponseVO.error(ResponseConstant.ERROR_CODE, String.format("缺少必要参数[%s]", ex.getParameterName()), "");
    }
    //捕获器2
    @ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
    public ResponseVO<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        if (log.isErrorEnabled()) {
            log.error(ex.getMessage(), ex);
        }
        BindingResult result = ex.getBindingResult();
        FieldError error = result.getFieldError();
        return ResponseVO.error(ResponseConstant.ERROR_CODE, null == error ? ResponseConstant.ERROR_MESSAGE : error.getDefaultMessage(), "");
    }
    //捕获器3
    @ExceptionHandler(value = {BindException.class})
    public ResponseVO<String> handleBindException(BindException ex) {
        if (log.isErrorEnabled()) {
            log.error(ex.getMessage(), ex);
        }
        BindingResult result = ex.getBindingResult();
        FieldError error = result.getFieldError();
        return ResponseVO.error(ResponseConstant.ERROR_CODE, null == error ? ResponseConstant.ERROR_MESSAGE : error.getDefaultMessage(), "");
    }
    //捕获器4
    @ExceptionHandler(value = {ConstraintViolationException.class})
    public ResponseVO<String> handleConstraintViolationException(ConstraintViolationException ex) {
        if (log.isErrorEnabled()) {
            log.error(ex.getMessage(), ex);
        }
        Optional<ConstraintViolation<?>> first = ex.getConstraintViolations().stream().findFirst();
        return ResponseVO.error(ResponseConstant.ERROR_CODE, first.isPresent() ? first.get().getMessage() : ResponseConstant.ERROR_MESSAGE, "");
    }
    //其他所有异常捕获器
    @ExceptionHandler(Exception.class)
    public ResponseVO<String> otherErrorDispose(Exception e) {
        // 打印错误日志
        log.error("错误代码({}),错误信息({})", ResponseConstant.ERROR_CODE, e.getMessage());
        e.printStackTrace();
        return ResponseVO.error(ResponseConstant.ERROR_CODE, ResponseConstant.ERROR_MESSAGE, e.getMessage());
    }
}

自定义异常返回和自定义常量

//自定义接口响应类
@Data
public class ResponseVO<T> implements Serializable {
    // 状态码: 0-成功,其他-失败
    private final Integer code;
    // 返回信息
    private final String message;
    //返回值
    private final T data;
    //是否成功
    private final Boolean success;
    // 成功返回
    public static <T> ResponseVO<T> success(T data) {
        return new ResponseVO<>(data);
    }
    // 失败返回
    public static <T> ResponseVO<T> error(Integer code, String message, T data) {
        return new ResponseVO<>(code, message, data);
    }
    public ResponseVO(T data) {
        this.code = ResponseConstant.SUCCESS_CODE;
        this.message = ResponseConstant.OK;
        this.data = data;
        this.success = true;
    }
    public ResponseVO(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
        this.success = code == ResponseConstant.SUCCESS_CODE;
    }
}
//自定义常量类
public class ResponseConstant {
    public static final String OK = "OK";
    public static final String ERROR = "error";
    public static final int SUCCESS_CODE = 200;
    public static final int ERROR_CODE = 500;
    public static final String ERROR_MESSAGE = "操作失败!!";
}

3.可能会遇到的问题

这里我就不介绍使用方式了,网上有很多详细的案例,包括每个注解的作用介绍,分组校验和自定义校验的使用方法(话说我自己都没有用过,只用过简单的注解)。

3.1注解不生效

注解不生效的情况有很多,主要参考的解决思路:jar包冲突、加错注解、少了关键注解。
(1)jar包冲突可能是引入的时候引入了多个版本的jar包,注意检查pom,在springboot中只需要安装上述方式引入即可,无需再引入其他validator相关jar包。
(2)加错注解,主要是看你引入的注解是不是在下述这个路径下的。

(3)少了关键注解

//@Validated
@RestController
@RequestMapping("test")
public class TestController {
    @GetMapping("test1")
    public ResponseVO<Integer> test2(@NotNull(message = "最小值不能为空") Integer minNum,
                                     @NotNull(message = "最大值不能为空") @Min(value = 10,message = "参数必须大于10") Integer maxNum) {
        return ResponseVO.success(11);
    }
}

如上明明写了@NotNull,也确认了引入的注解是对的,但是就是不返回错误信息。像这种参数没有放在一个对象中,而是直接写在接口上的情况,需要在类上加@Validated注解,否则不会生效。

@Validated
@RestController
@RequestMapping("test")
public class TestController {
	@GetMapping("test3")
    public ResponseVO<Integer> test3(BlacklistPageParamVO vo) {
        return ResponseVO.success(11);
    }
    @PostMapping("test4")
    public ResponseVO<Integer> test4(@Valid @RequestBody BlacklistPageParamVO vo) {
        return ResponseVO.success(11);
    }
}
@Data
public class BlacklistPageParamVO{
    @NotBlank(message = "日期不能为空")
    private String date;
    @NotBlank(message = "日期2不能为空")
    private String date2;
    @Valid
    @NotNull(message = "内部对象不能为空")
    private InnerVO innerVO;
}
@Data
public class InnerVO {
    @NotNull(message = "num1不能为空")
    private Integer num1;
    @NotNull(message = "num2不能为空")
    private Integer num2;
}

第二种情况就是,我的校验字段在一个对象里面,这个时候需要在对象前面加上@Valid注解,所以test3的检验不会生效。这里扩展一下如果一个对象中还有另一个对象,且内部的对象也有需要检验的字段,需要给这个内部对象也加上@Valid注解才会生效。

3.2注解生效但返回的不是自定义信息

@Validated
@RestController
@RequestMapping("test")
public class TestController {
    @GetMapping("test1")
    public ResponseVO<Integer> test1(@NotNull(message = "最小值不能为空") @RequestParam Integer minNum,
                                     @NotNull(message = "最大值不能为空") @RequestParam Integer maxNum) {
        return ResponseVO.success(11);
    }
}

像上面这种情况如果在参数前加了@RequestParam表示参数必传,可以理解为作用和@NotNull是一样的。此时调用接口时,忘记传参数minNum了,我期待返回的是“最小值不能为空”,但是实际上返回的是“Required Integer parameter ‘minNum’ is not present”,且控制台打印的错误日志如下:
在这里插入图片描述
从错误名称,我们就可以看出,这是由于加了@RequestParam注解导致,让你传,你不传,所以报了这个错。这个错误对应的就是我们上文中的捕获器1。而且由此可以看出MissingServletRequestParameterException异常是比validation的异常优先级高的。

4.几个异常捕获器的作用

4.1捕获器1

MissingServletRequestParameterException
加了@RequestParam注解,但是接口调用时没有传指定的参数(注意:是没有传,而不是传了,但是值是null)。

4.2捕获器2

MethodArgumentNotValidException
经过测试,当校验的参数放在对象中,接口的请求方式是post请求,用@Valid @RequestBody方式接受参数时,如果报错,会被该捕获器捕获。

4.3捕获器3

BindException
经过测试,当校验参数写在类中,接口请求方式是get请求时,报错会被该捕获器捕获。

4.4捕获器4

ConstraintViolationException
传了值,但是不符合要求。@NotNull(message = “最大值不能为空”) @Min(value = 10,message = “参数必须大于10”),要求传非null值,且值必须大于10,否则会返回错误信息。经过测试,当校验参数直接写在接口上,而不是写在类中,报错会被该捕获器捕获。

4个捕获器对应4种不同的场景。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值