添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
帅气的高山  ·  44. ...·  9 月前    · 
爱健身的灭火器  ·  Python ...·  11 月前    · 

本文主要介绍SpringBoot中如何使用@Validated,@Valid及其相关注解,以及全局异常捕获对接口入参进行优雅校验和返回自定义异常返回客户端。

接口实体类如下

@Data
public class User implements Serializable {
    @NotBlank(message = "姓名不能为空", groups = {ValidateType.SELECT.class})
    private String name;
    @Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}", message = "手机号格式不正确")
    private String phone;
    @Positive(message = "要为正数")
    @Max(value = 99, message = "不能超过99", groups = ValidateType.SELECT.class)
    private Integer age;
    @NotNull(message = "token不能为null", groups = ValidateType.INSERT.class)
    private String token;
    @NotBlank(message = "openUid不能为blank")
    @Length(min = 5, max = 10, message = "范围为5--10之间")
    private String openUid;
    @NotEmpty(message = "address不能为empty")
    private String address;
    @Email(message = "邮箱格式错误",regexp = "^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")
    @NotBlank(message = "邮箱不能为空")
    private String email;
    @Future(message = "日期必须是将来某个时候", groups = ValidateType.class)
    @NotNull(message = "日期不能为空", groups = ValidateType.class)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss",iso = DateTimeFormat.ISO.DATE_TIME)
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalTime createTime;
    @JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "HH:mm:ss")
    private LocalTime updateTime;
    @Valid
    @NotNull(message = "userList不能为空")
    private List<UserVO> userVOList;
    @Valid
    @NotNull(message = "userVo不能为空")
    private UserVO userVO;
@Data
public class UserVO {
    @NotNull(message = "姓名不能为空")
    @Length(min = 5,max = 10)
    private String name;
    @Positive
    @Max(value = 100,message = "你真的100岁了吗?")
    @Min(value = 0,message = "怎么会有负数岁数的人呢?")
    private Integer age;
    @Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}", message = "userVO手机号格式不正确")
    private String phoneNumber;
	@NotBlank(message = "地址不能为空")
    private String address;
//    @Email(message = "邮箱格式错误")
    @Pattern(regexp = "^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$",message = "userVO邮箱正则校验失败")
    @NotBlank(message = "vo邮箱不能为空")
    private String email;
    @PastOrPresent(message = "日期为过去或者现在")
    @NotNull(message = "日期不能为空")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
//    @JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd" ,timezone = "GMT+8")
    private LocalDateTime createTime;

以上为两个实体类。在user中嵌套了userVo,也可以进行嵌套验证。

注解解释
@Null限制只能为null
@NotEmpty验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@NotNull限制必须不为null
@AssertFalse限制必须为false
@AssertTrue限制必须为true
@DecimalMax(value)限制必须为一个不大于指定值的数字
@DecimalMin(value)限制必须为一个不小于指定值的数字
@Digits(integer,fraction)限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future限制必须是一个将来的日期
@Past限制必须是一个过去的日期
@Max(value)限制必须为一个不大于指定值的数字
@Min(value)限制必须为一个不小于指定值的数字
@Pattern(value)限制必须符合指定的正则表达式
@Size(max,min)限制字符长度必须在min到max之间
@Email验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

以上表格包括了常用的一些字段校验。全部的注解在以下位置

一、分组校验

  1. 配置分组接口类
    在这里定义了增删改查四种业务类型
	public interface ValidateType {
    public interface INSERT {};
    public interface SELECT {};
    public interface UPDATE {};
    public interface DELETE {};
  1. 在需要分组校验的字段上加上对应的分组接口
    例如要在查询接口上校验姓名字段,并且不能为空;可以如下标注字段,groups表示我想在查询的接口上对name进行校验,
	public class User implements Serializable {
		    @NotBlank(message = "姓名不能为空", groups = {ValidateType.SELECT.class})
    		private String name;
  1. 控制器对应的接口配置,在请求体中加上@Validated(ValidateType.SELECT.class),表示这个接口入参要对标记了这个分组的字段进行相应的校验,不会校验其他类型。
    @PostMapping(value = "/select")
    public Result findUser(@RequestBody @Validated(ValidateType.SELECT.class) User user){
        try {
            return ResultUtil.successData(user);
        } catch (Exception e) {
            return ResultUtil.failureMsg(ResultEnum.SQL_FAILURE.getMsg());

可以看到在select接口中 ,如果没有name字段,或者字段值为空字符串,或者为空格,都会触发我们配置的校验规则,返回对应的message。

可以在其他接口中再测试一下。我们对name字段配置的是 ValidateType.SELECT.class 类型。我们在更新接口中测试一下

	@PostMapping(value = "/update")
    public Result update(@RequestBody @Validated(ValidateType.UPDATE.class) User user) {
        try {
            return ResultUtil.successData(user);
        } catch (Exception e) {
            return ResultUtil.failureMsg(ResultEnum.SQL_FAILURE.getMsg());

更新接口中我们配置的是 @Validated(ValidateType.UPDATE.class) 这种校验规则。此时name字段就会被忽略校验,因为name字段是ValidateType.SELECT.class 类型的规则。验证一下

在这里插入图片描述
可以看到请求体中没有name,依旧请求成功
在这里插入图片描述
加上name字段,但是为空格也请求成功,如果是空字符串依旧成功。这里不做演示了。

通过例子以上可对其他字段做对应的校验,并且加入对应的校验分组,可以灵活的控制不同接口不同字段的校验规则。

二、默认校验分组

框架为我们配置了默认的分组,如果我们不手动配置分组,则会进行默认分组校验。openUid字段没有加入任何一个分组,下面我们对上述的openUid字段进行验证一下。
我们 首先新建一个all接口

	@PostMapping(value = "/all")
    public Result all(@RequestBody @Validated User user) {
        try {
            return ResultUtil.successData(user);
        } catch (Exception e) {
            return ResultUtil.failureMsg(ResultEnum.SQL_FAILURE.getMsg());

这个接口中@Validated 注解没有任何分组。我们加上一个有效的openUid字段请求一下,看会发生什么

在这里插入图片描述
可以看到请求成功并且返回了请求体。

去掉该字段就会报错
在这里插入图片描述
如果是个空字符串或者空格,依旧报错
在这里插入图片描述
在这里插入图片描述
这里返回了:范围在5–10之间,这是因为我们在上面配置了@length 注解,范围最小是5,最大是10。

如果将接口中的验证分组加上,结果将会怎么样呢?
	@PostMapping(value = "/all")
    public Result all(@RequestBody @Validated(ValidateType.class) User user) {
        System.out.println(user);
        int i = 0;
        try {
            return ResultUtil.successData(user);
        } catch (Exception e) {
            return ResultUtil.failureMsg(ResultEnum.SQL_FAILURE.getMsg());

这里加上了ValidateType.class 这个分组,openUid上没有任何分组,验证一下

在这里插入图片描述
可以看到没有openUid字段,是请求成功的。

	从以上例子可以得出,分组配置是严格控制的,每个分组只会校验自己分组下的字段。
	就算是默认分组也不会对其他分组造成干扰,这样就对字段进行了严格的分组和校验。

三、嵌套校验

一般项目中会根据业务需要,要定义一些嵌套对象,比如人这个实体会包括以下属性,姓名、性别、等等,也会有所有房屋、儿女、汽车等等实体。其中属性可以是单个对象,也可以是对象的集合List,这种场景就可以使用嵌套校验。

上述我们定义了另外一个userVO,并且在user中将其注入为一个userVO和一个userVO的List集合,要进行嵌套校验则可以进行如下配置

    @Valid
    @NotNull(message = "userList不能为空")
    private List<UserVO> userVOList;
    @Valid
    @NotNull(message = "userVo不能为空")
    private UserVO userVO;

在嵌套对象和list上加上@Valid。表示要对这个对象进行嵌套校验。在userVO中每个字段可以有对应的自己的校验规则。
配置好后我们对userVO的phoneNumber字段进行验证,手机号我们配置的是正则表达式,可以对其进行验证一下。

    @Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}", message = "手机号格式不正确")
    private String phoneNumber;
	@PostMapping(value = "/recursive")
    public Result recursive(@RequestBody(required = true) @Validated User user) {
        try {
            return ResultUtil.successData(user);
        } catch (Exception e) {
            return ResultUtil.failureMsg(ResultEnum.SQL_FAILURE.getMsg());

在这里插入图片描述
可以看到请求成功,改变一下phoneNumber的值,就会报错
在这里插入图片描述
如果请求参数中没有userVO,就会报错
在这里插入图片描述

1、@Valid和@NotNull的组合使用

上面我们在userVO字段上使用了@Valid、@NotNull(message = “userVO不能为空”)两个组合注解,
如果我们只加其中一个会怎么样呢?验证一下

    @Valid
    private UserVO userVO;

1.1只加上@Valid 注解

我们先只加上@Valid 注解
在这里插入图片描述
我们全部字段都符合校验规则,请求成功

1.1.1 不加userVO对象

接下来我们在请求中不加userVO。结果成功
在这里插入图片描述

1.1.2 加上正确的userVO对象
1.1.3 加上错误的userVO对象

加上userVO,但是手机号字段输入错误的,结果触发了校验规则
在这里插入图片描述

1.2 只加@NotNull 注解

    @NotNull(message = "userVo不能为空")
    private UserVO userVO;
1.2.1 不加userVO对象

触发了@NotNull 校验规则
在这里插入图片描述

1.2.2 加上正确的userVO对象
1.2.3 加上错误的userVO对象

依旧成功。并没有触发校验规则
在这里插入图片描述

四、全局异常捕获

1、配置全局异常捕获类

@Slf4j
@RestControllerAdvice
public class GlobalException {
     * 校验URL?param=value,参数key是否缺失
     * @param e 忽略参数异常
     * @return Result
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Result parameterMissingHandler(MissingServletRequestParameterException e) {
        return ResultUtil.failureMsg("请求参数 " + e.getParameterName() + " 不能为空");
     * @Author: zhanggeyang
     * @Description:校验URL?param=value,参数value是否缺失
     * @Date: 4:09 下午 5/13/23
     * @Param: [e]
     * @Return: com.example.mybatisplustest.entity.Result
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ConstraintViolationException.class})
    public Result otherExceptionHandler(ConstraintViolationException e) {
        // 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
        if (!StringUtils.isEmpty(e.getMessage())) {
            return ResultUtil.failureMsg(e.getMessage());
        return ResultUtil.failure(ResultEnum.SERVICE_FAILURE);
     * 缺少请求体异常处理器
     * @param e 缺少请求体异常
     * @return Result
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Result parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
//        log.info("出错了:{}", e.getLocalizedMessage());
        Throwable rootCause = e.getRootCause();
        //校验参数是否多余,导致不能识别
        if (rootCause instanceof JsonProcessingException) {
            JsonMappingException mappingException = (JsonMappingException) rootCause;
            String fieldName = mappingException.getPath().get(0).getFieldName();
            return ResultUtil.failureMsg("参数 " + fieldName + " 不能识别");
        //校验日期字段转换是否异常
        if (rootCause instanceof DateTimeException) {
            return ResultUtil.failureMsg("日期转换异常");
        //如果都不属于,则属于请求体缺失
        return ResultUtil.failureMsg("请求体缺失");
     * 嵌套对象字段缺失会走这里
     * @param e 参数验证异常
     * @return ResponseInfo
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result parameterExceptionHandler(MethodArgumentNotValidException e) {
//        log.error("", e);
        // 获取异常信息
        BindingResult exceptions = e.getBindingResult();
        // 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
        if (exceptions.hasErrors()) {
            List<ObjectError> errors = exceptions.getAllErrors();
            if (!errors.isEmpty()) {
                // 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可
                FieldError fieldError = (FieldError) errors.get(0);
                return ResultUtil.failureMsg(fieldError.getDefaultMessage());
        return ResultUtil.failure(ResultEnum.SERVICE_FAILURE);
     * @Author: zhanggeyang
     * @Description:  捕获请求方法异常,比如post接口使用了get
     * @Date: 8:20 下午 5/13/23
     * @Param: [e]
     * @Return: com.example.mybatisplustest.entity.Result
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result methodNotAllowedHandler(HttpRequestMethodNotSupportedException e) {
        String method = e.getMethod();
        return ResultUtil.failureMsg(method + "请求方法不被允许");

上述配置基本涵盖了我们校验中的常用异常
比如我们输入的时间格式和配置的yyyy-MM-dd HH:mm:ss不一样,就会抛出日期转换异常
在这里插入图片描述

比如这个接口,两个参数可以进行校验分为key不存在和value不存在两种情况

    @GetMapping(value = "/p")
    public Result p(@RequestParam(value = "p") @NotBlank(message = "p不能为空") String p, @RequestParam(value = "n") @NotBlank(message = "n不能为空") String string) {
        try {
            return ResultUtil.successMsg(p + "==" + string);
        } catch (Exception e) {
            return ResultUtil.failureMsg(ResultEnum.SQL_FAILURE.getMsg());

1、分组校验

需要在要校验的字段上加上校验规则和对应的分组。并且每个分组之间是互相隔离的,不会影响其他的分组。没有分组的就会被加入框架默认分组。

2、嵌套校验

嵌套校验可以加载对应的对象和对象集合上。需要@Valid和@NotNull注解配合使用。

2.1 只有@Valid

请求中不加对应的对象,或者加上正确的对象,都会请求成功。
加上错误的对象,就会触发具体的字段校验规则。

2.2 只有@NotNull

不加对应的对象只会触发非空校验
加上正确或者错误的对象,都会请求成功,不会触发具体的字段校验规则。

综上所述,在嵌套校验中要成功校验,需要2个注解同时使用,才能保证非空判断和具体的字段校验都成功。

以上就是字段校验结合全局异常捕获的使用。

系列文章目录 文章目录系列文章目录一、@Valid和@Validated的介绍1.引入jar包2. @Valid和@Validated的作用3.@Valid和@Validated的区别4.常用的参数校验解二、@Valid和@Validated使用1. 捕获全局异常配置类2.单个接口参数校验3.接口参数校验提示4.嵌套类解提示4.1. 实体类数据4.2.接口校验4.3. 接口调用5. 分组校验5.1 校验的实体类数据5.2 接口校验5.3 接口调用1.提交学习刷题试卷接口演示2.提交文档学习接口演示6
@Valid 是JSR303 指定的标准 ,hibernate 对其做了实现。登录后复制 <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-vali...
SpringBoot全局异常捕获处理及参数校验 为什么要用全局异常处理? 在日常开发中,为了不抛出异常堆栈信息给前端页面,每次编写Controller层代码都要尽可能的catch住所有service层、dao层等异常,代码耦合性较高,参数校验逻辑业务逻辑还长,不利于后期维护。 为解决该问题,可以将Controller层异常信息统一封装处理,且能区分对待Controller层方法返回给前端。 如何进行全局异常捕获和处理? 一共有两种方法: Spring的AOP @ControllerAdvice结合@Exc
网上看了很多资料但是都没有解决我的问题,最后通过查看异常信息解决问题,这里做相关记录以备后用,怕忘了!也给需要的同志们参考。 首先我们校验如果错误会抛出ConstraintViolationException异常,但是在全局异常处理中如下编写无法捕获: @ResponseBody @ExceptionHandler(ConstraintViolationException.class) public Map<String,Object> handleCve(ConstraintViolat
本文带你了解如何正确地使用@NotEmpty、@NotBlank等解、@Validated和@Valid的区别、解决@NotBlank等解不生效的问题、使用BindingResult进行(controller)接口请求参数的统一校验。 为什么使用解呢?因为服务端通常将`controller层`作为调用的第一层,因而参数校验常常在这里完成。假如在controller层的某个方法中,有很多个需要校验的请求参数,这样写无疑会有多条if判断语句,因而,我们需要使用优雅的方式处理接口请求参数。......
目的是优雅的实现参数校验,避免使用if-else。 @Valid是javax提供的,可以用在方法、构造函数、方法参数和成员属性(字段)上。可实现嵌套验证。 @Validates是spring框架validation类提供的,可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上。可实现分组验证。 常用校验 实体中参数需要参数校验解,比如@NotNull等,在文章最后会做一个总结。 @Data @TableName("tb_brand") public class BrandEntity imple
`@Validated` 统一参数检验 javax.validation.ConstraintViolationException org.springframework.validation.BindException org.springframework.web.bind.MethodArgumentNotValidException 参数检验异常分析
`@Validated` 和 `@Valid` 都是用于校验数据的解,但是它们的使用场景和校验规则略有不同。 `@Validated` 是 Spring 提供的校验解,它可以用于方法、构造函数、类以及接口上。它的作用是告诉 Spring 在执行方法或构造函数时需要进行数据校验校验规则是通过在方法参数上添加校验解来实现的。`@Validated` 支持分组校验和级联校验。 `@Valid` 则是 Java 标准库中的校验解,它只能用于方法参数、字段、方法返回值等元素上。它的作用是告诉 Java 校验框架对该元素进行数据校验校验规则是通过在该元素上添加校验解来实现的。`@Valid` 没有分组校验和级联校验的功能。 `@Validated` 支持的校验解有: - `@NotNull`:验证对象不为 null,无法查检长度为 0 的字符串 - `@NotEmpty`:验证对象不为 null,长度不为 0 - `@NotBlank`:验证对象不为 null,去除首位空格后,长度不为 0 而 `@Valid` 支持的校验解有: - `@NotNull`:验证对象不为 null,无法查检长度为 0 的字符串 - `@AssertTrue`:验证 Boolean 对象是否为 true - `@Size`:验证对象(Array, Collection, Map, String)长度是否在指定范围内 - `@Min`:验证 Number 和 String 对象是否大等于指定的值 - `@Max`:验证 Number 和 String 对象是否小等于指定的值