zoukankan      html  css  js  c++  java
  • SpringBoot Validation优雅的全局参数校验

    前言

    我们都知道在平时写controller时候,都需要对请求参数进行后端校验,一般我们可能会这样写

    public String add(UserVO userVO) {
        if(userVO.getAge() == null){
            return "年龄不能为空";
        }
        if(userVO.getAge() > 120){
            return "年龄不能超过120";
        }
        if(userVO.getName().isEmpty()){
            return "用户名不能为空";
        }
        // 省略一堆参数校验...
        return "OK";
    }
    

    业务代码还没开始写呢,光参数校验就写了一堆判断。这样写虽然没什么错,但是给人的感觉就是:不优雅,不专业,代码可读性也很差,一看就是新手写的代码

    作为久经战争的老司机怎么能这样呢,大神是不允许这样代码出现的,其实SpringBoot提供整合了参数校验解决方案spring-boot-starter-validation

    整合使用

    在SpringBootv2.3之前的版本只需要引入 web 依赖就可以了他包含了validation校验包在此之后SpringBoot版本就独立出来了需要单独引入依赖

    <!--参数校验-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-validation</artifactId>
            </dependency>
    

    内置的校验注解有很多,罗列如下:

    注解 校验功能
    @AssertFalse 必须是false
    @AssertTrue 必须是true
    @DecimalMax 小于等于给定的值
    @DecimalMin 大于等于给定的值
    @Digits 可设定最大整数位数和最大小数位数
    @Email 校验是否符合Email格式
    @Future 必须是将来的时间
    @FutureOrPresent 当前或将来时间
    @Max 最大值
    @Min 最小值
    @Negative 负数(不包括0)
    @NegativeOrZero 负数或0
    @NotBlank 不为null并且包含至少一个非空白字符
    @NotEmpty 不为null并且不为空
    @NotNull 不为null
    @Null 为null
    @Past 必须是过去的时间
    @PastOrPresent 必须是过去的时间,包含现在
    @Pattern 必须满足正则表达式
    @PositiveOrZero 正数或0
    @Size 校验容器的元素个数

    单个参数校验

    使用很简单只需要在需要校验controller上加上@Validated注解在需校验参数上加上@NotNull,@NotEmpty之类参数校验注解就行了,

    @Validated
    @GetMapping("/home")
    public class ProductController {
      public Result index(@NotBlank String name, @Email @NotBlank String email) {
            return ResultResponse.success();
        }
    }
    

    对象参数校验

    在上面的基础上只需要在对象参数前面加上@Validated注解,然后在需要校验的对象参数的属性上面加上
    @NotNull,@NotEmpty之类参数校验注解就行了,

     @PostMapping("/user")
    public Result index1(@Validated @RequestBody UserParams userParams) {
            log.info("info  test######");
            log.error("error test #####");
            return ResultResponse.success(userParams);
        }
    
    @Data
    public class UserParams {
    
        @NotBlank
        private String username;
        private int age;
        @NotBlank
        private String addr;
        @Email
        private String email;
    
    
    
    }
    

    参数校验异常信息处理

    上面我们进行了参数校验,默认当参数校验没通过后会通过异常方式来抛出错误信息MethodArgumentNotValidException是在校验时抛出的还包括很多其他异常信息,这时我们可以通过全局异常捕获信息来处理这些参数校验异常

    全局异常处理类只需要在类上标注@RestControllerAdvice,并在处理相应异常的方法上使用@ExceptionHandler注解,写明处理哪个异常即可

    package cn.soboys.core;
    
    import cn.hutool.core.collection.CollectionUtil;
    import cn.hutool.core.util.StrUtil;
    import cn.soboys.core.authentication.AuthenticationException;
    import cn.soboys.core.ret.Result;
    import cn.soboys.core.ret.ResultCode;
    import cn.soboys.core.ret.ResultResponse;
    import org.springframework.validation.BindException;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.HttpMediaTypeNotSupportedException;
    import org.springframework.web.HttpRequestMethodNotSupportedException;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    import org.springframework.web.servlet.NoHandlerFoundException;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.ConstraintViolationException;
    import javax.validation.Path;
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    /**
     * @author kenx
     * @version 1.0
     * @date 2021/6/17 20:19
     * 全局异常统一处理
     */
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        /**
         * 处理 json 请求体调用接口对象参数校验失败抛出的异常
         */
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public Result jsonParamsException(MethodArgumentNotValidException e) {
            BindingResult bindingResult = e.getBindingResult();
            List errorList = CollectionUtil.newArrayList();
    
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                String msg = String.format("%s%s;", fieldError.getField(), fieldError.getDefaultMessage());
                errorList.add(msg);
            }
            return ResultResponse.failure(ResultCode.PARAMS_IS_INVALID, errorList);
        }
    
    
        /**
         * 处理单个参数校验失败抛出的异常
         */
        @ExceptionHandler(ConstraintViolationException.class)
        public Result ParamsException(ConstraintViolationException e) {
    
            List errorList = CollectionUtil.newArrayList();
            Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
            for (ConstraintViolation<?> violation : violations) {
                StringBuilder message = new StringBuilder();
                Path path = violation.getPropertyPath();
                String[] pathArr = StrUtil.splitToArray(path.toString(), ".");
                String msg = message.append(pathArr[1]).append(violation.getMessage()).toString();
                errorList.add(msg);
            }
            return ResultResponse.failure(ResultCode.PARAMS_IS_INVALID, errorList);
        }
    
        /**
         * @param e
         * @return 处理 form data方式调用接口对象参数校验失败抛出的异常
         */
        @ExceptionHandler(BindException.class)
        public Result formDaraParamsException(BindException e) {
            List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
            List<String> collect = fieldErrors.stream()
                    .map(o -> o.getField() + o.getDefaultMessage())
                    .collect(Collectors.toList());
            return ResultResponse.failure(ResultCode.PARAMS_IS_INVALID, collect);
        }
    
        /**
         * 请求方法不被允许异常
         */
        @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
        public Result httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
            return ResultResponse.failure(ResultCode.METHOD_NOT_ALLOWED);
        }
    
        /**
         * @param e
         * @return Content-Type/Accept 异常
         * application/json
         * application/x-www-form-urlencoded
         */
        @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
        public Result httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
            return ResultResponse.failure(ResultCode.BAD_REQUEST);
        }
    
        /**
         * handlerMapping  接口不存在跑出异常
         *
         * @param e
         * @return
         */
        @ExceptionHandler(NoHandlerFoundException.class)
        public Result noHandlerFoundException(NoHandlerFoundException e) {
            return ResultResponse.failure(ResultCode.NOT_FOUND, e.getMessage());
        }
    
    
        /**
         * 认证异常
         * @param e
         * @return
         */
        @ExceptionHandler(AuthenticationException.class)
        public Result UnNoException(AuthenticationException e) {
            return ResultResponse.failure(ResultCode.UNAUTHORIZED,e.getMessage());
        }
    
        /**
         *
         * @param e 未知异常捕获
         * @return
         */
        @ExceptionHandler(Exception.class)
        public Result UnNoException(Exception e) {
            return ResultResponse.failure(ResultCode.INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }
    

    这里关于全局异常处理请参考我这篇SpringBoot优雅的全局异常处理
    这里我返回的是自定义响应体api 请参考我这篇Spring Boot 无侵入式 实现RESTful API接口统一JSON格式返回

    当然校验异常的处理还有其它方式,当参数在没有通过校验情况下会帮我们把错误信息注入到BindingResult对象中,会注入到对应controller方法,这个仅限于参数有@RequestBody或者@RequestParam修饰的参数才行

    public String add1(@Validated UserVO userVO, BindingResult result) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        if(!fieldErrors.isEmpty()){
            return fieldErrors.get(0).getDefaultMessage();
        }
        return "OK";
    }
    

    当然我推荐第一种可以通过全局异常处理的方式统一处理校验异常

    如果每个Controller方法中都写一遍对BindingResult信息的处理,使用起来还是很繁琐。代码很冗余

    当我们写了@validated注解,不写BindingResult的时候,SpringBoot 就会抛出异常。由此,可以写一个全局异常处理类来统一处理这种校验异常,从而免去重复组织异常信息的代码。

    关注公众号猿人生获取更多干货分享
    image

  • 相关阅读:
    数组常用遍历方法总结
    文本控制行数,超出省略号显示
    数据结构入门
    数论函数补充 公式推导
    几何入门合集 gym101968 problem F. Mirror + gym102082 Problem F Fair Chocolate-Cutting + gym101915 problem B. Ali and Wi-Fi
    COCI 2018/2019 CONTEST #2 T4 Maja T5Sunčanje Solution
    数论函数
    数论入门
    USACO1.4 1.5 搜索剪枝与数字 洛谷OJ P1214 P1215 P1217 P1218
    USACO Section 1.3 题解 (洛谷OJ P1209 P1444 P3650 P2693)
  • 原文地址:https://www.cnblogs.com/kenx/p/14975394.html
Copyright © 2011-2022 走看看