关于springboot:SpringBoot-Validation优雅的全局参数校验

34次阅读

共计 6160 个字符,预计需要花费 16 分钟才能阅读完成。

前言

咱们都晓得在平时写 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 就会抛出异样。由此,能够写一个全局异样解决类来对立解决这种校验异样,从而免去反复组织异样信息的代码。

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

正文完
 0