Spring Boot @Validated 和Javax的@Valid配合使用

一、@Validated 和@Valid有什么用

@Validation 和@Valid 常常配合使用对传输的参数进行数据校验的注解,并通过配置全局异常处理器进行合理化的提示,增加用户的体验

并且@Validated可以通过分组来指定什么时候触发什么样的参数校验(这里看一下就行,下面有说什么是分组)

二、为什么要使用@Validated 和@Valid的思考?

其实不用这两个注解也可以完成对传输的参数进校验,那样我们就需要一直写if语句进行判断 ,如果不为xxx,抛出异常,然后进行捕获处理

但是当多处都用到的一样的传输参数的时候 ,我们每次都需要写一些重复的if进行校验,其实代码是不优雅的。因此有了这两个组件来帮我们进行传输参数的校验。

三、使用方法

3.1 引入pom

java 复制代码
<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

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

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

       <!--测试方法-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
		</dependency>
	</dependencies>

3.2 写实体类加入一写校验注解

常用的校验注解


java 复制代码
package com.sofwin.validator.domain;

import com.sofwin.validator.config.InsertGroup;
import com.sofwin.validator.config.Status;
import com.sofwin.validator.config.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.springframework.web.bind.annotation.Mapping;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.util.List;

/**
 * @packageName: com.sofwin.validator.domain
 * @author: wentao
 * @date: 2023/9/4 21:17
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 测试
 */
@Data
public class UserR {

    @NotBlank(message = "名称不能为空")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Range(min = 1,max = 200,message ="最小为{min}岁,最大为{max}岁" )
    private Integer age;

    @Size(message ="编号长度为 [4-8] ", min = 4, max = 8)
    private String idNo;

}

3.3 编写controller层进行测试 (请求参数是对象)

注意需要在请求参数的前面加上@Valid注解

统一返回类

java 复制代码
package com.sofwin.validator.config;

import lombok.Data;

/**
 * @packageName: com.sofwin.validator.config
 * @author: wentao
 * @date: 2023/9/4 21:34
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 统一返回参数
 */
@Data
public  class  BaseResult<T> {
    private int code;
    private String message;
    private T data;

    public BaseResult() {
    }

    public BaseResult(int code, T data,String message) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> BaseResult<T> build(int code, T data, String message) {
        return new BaseResult<T>(code,data,message);
    }

    public static <T> BaseResult<T> build( T data, BaseResult resultCodeEnum) {
        return new BaseResult<T>(resultCodeEnum.getCode(),data,resultCodeEnum.getMessage());
    }

    public static <T> BaseResult<T> ok(T data) {
        return new BaseResult<>(20000,data,"success");
    }

    public static <T> BaseResult<T> fail(T data) {
        return new BaseResult<>(50000,data,"error");
    }



}

controller

java 复制代码
package com.sofwin.validator.controller;

import com.sofwin.validator.config.BaseResult;
import com.sofwin.validator.config.InsertGroup;
import com.sofwin.validator.domain.UserR;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

/**
 * @packageName: com.sofwin.validator.controller
 * @author: wentao
 * @date: 2023/9/4 21:14
 * @version: 1.0
 * @description: 请求参数校验
 */

@RestController
@RequestMapping("validator")
public class ValidatorController {


    @PostMapping("/validPost")
    public BaseResult validPostTest(@Valid @RequestBody UserR user ) {
       return BaseResult.ok(user);
    }
   
}

正常参数

非正常参数

我们发现没有提示我们在实体中message中写的提示信息,是因为我们没有设置全局异常处理器

,它只是在控制台返回了提示信息

2023-09-05 21:24:03.305 WARN 20924 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.sofwin.validator.config.BaseResult com.sofwin.validator.controller.ValidatorController.validPostTest(com.sofwin.validator.domain.UserR): [Field error in object 'userR' on field 'age': rejected value [1111]; codes [Range.userR.age,Range.age,Range.java.lang.Integer,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userR.age,age]; arguments []; default message [age],200,1]; default message [最小为1岁,最大为200岁]] ]

全局异常处理器

java 复制代码
package com.sofwin.validator.config;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;
import org.slf4j.Logger;

/**
 * @packageName: com.sofwin.validator.config
 * @author: wentao
 * @date: 2023/9/4 21:28
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 全局异常处理器
 */

@RestControllerAdvice
@Slf4j
public class GlobExceptionHandeler {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    //valid参数校验出现异常
    @ExceptionHandler(BindException.class)
    public BaseResult bindException(BindException e) {
        logger.error("valid参数校验出现异常");
        System.out.println(e);
        return BaseResult.build(441,null,e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }

    //validated参数校验出现异常
    @ExceptionHandler(ConstraintViolationException.class)
    public BaseResult constraintViolationException(ConstraintViolationException e) {
        logger.error("validated参数校验出现异常");

        return BaseResult.build(441,null,e.getLocalizedMessage().split(":")[1].trim());
    }

    @ExceptionHandler(Exception.class)
    public BaseResult constraintViolationException(Exception e) {
        logger.error("Exception");
        return BaseResult.build(441,null,e.getLocalizedMessage());
    }
}

加入全局异常处理器后非正常参数返回结果:

3.4 当参数不是一个对象

一定要在controller上加入@Validated才生效

java 复制代码
@RestController
@RequestMapping("validator")
@Validated
public class ValidatorController {


    @PostMapping("/validPost")
    public BaseResult validPostTest(@Valid @RequestBody UserR user ) {
       return BaseResult.ok(user);
    }
  

    @GetMapping("/validGet1")
    public BaseResult validGetTest( @NotBlank(message = "名字不能为空") String name ) {
        return BaseResult.ok(name);
    }
}

3.5 分组

当我们在特定情况下才进行参数校验才进行分组,例如只有当我们插入的时候我们进行校验,其他时候不进行参数的校验,这个时候就可以使用分组

实体类

java 复制代码
package com.sofwin.validator.domain;

import com.sofwin.validator.config.InsertGroup;
import com.sofwin.validator.config.Status;
import com.sofwin.validator.config.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.springframework.web.bind.annotation.Mapping;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.util.List;

/**
 * @packageName: com.sofwin.validator.domain
 * @author: wentao
 * @date: 2023/9/4 21:17
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 测试
 */
@Data
public class UserR {

    @NotBlank(message = "名称不能为空")
    //进行分组
    @NotBlank(message = "名称不能为空(InsertGroup)",groups = InsertGroup.class)
    private String name;

    @NotNull(message = "年龄不能为空")
    @NotNull(message = "年龄不能为空(InsertGroup)",groups = {InsertGroup.class,UpdateGroup.class})
    @Range(min = 1,max = 200,message ="最小为{min}岁,最大为{max}岁" )
    private Integer age;

   

    @Size(message ="编号长度为 [4-8] ", min = 4, max = 8)
    private String idNo;



}

其中InsertGroup和UpdateGroup只是一个普通的接口

java 复制代码
public interface InsertGroup {

}



public interface UpdateGroup {

}

controller

java 复制代码
package com.sofwin.validator.controller;

import com.sofwin.validator.config.BaseResult;
import com.sofwin.validator.config.InsertGroup;
import com.sofwin.validator.domain.UserR;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
 * @packageName: com.sofwin.validator.controller
 * @author: wentao
 * @date: 2023/9/4 21:14
 * @version: 1.0
 * @description: 请求参数校验
 */

@RestController
@RequestMapping("validator")
@Validated
public class ValidatorController {


    @PostMapping("/validPost")
    public BaseResult validPostTest(@Valid @RequestBody UserR user ) {
       return BaseResult.ok(user);
    }
    @PostMapping("/validPost2")
    public BaseResult validPostTest2(@Validated(InsertGroup.class) @RequestBody UserR user ) {
        return BaseResult.ok(user);
    }

    @GetMapping("/validGet1")
    public BaseResult validGetTest( @NotBlank(message = "名字不能为空") String name ) {
        return BaseResult.ok(name);
    }

    @GetMapping("/validGet2")
    public BaseResult validGetTest2(@Validated(InsertGroup.class) @NotNull(message = "名字不能为空") String name ) {
        return BaseResult.ok(name);
    }
}

测试

复制代码
validPost2、validGet2

validPost2

validGet2

3.6 自定义注解

当它提供的注解我们没有办法解决我们的问题的时候,我们就可以自定义注解

定义注解Status

java 复制代码
package com.sofwin.validator.config;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @packageName: com.sofwin.validator.config
 * @author: wentao
 * @date: 2023/9/4 21:52
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 自定义校验注解
 */
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//自己写校验规则
@Constraint(validatedBy = {StatusConstraint.class})
//元注解 可以分组使用,如果不写定义相同的注解会出现错误
@Repeatable(Status.List.class)
public @interface Status {

    String[] statusType() default {};

    String message() default "状态传递有误";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload()  default {};

   @Target({ElementType.FIELD,ElementType.PARAMETER})
   @Retention(RetentionPolicy.RUNTIME)
   @Documented
    @interface List {
       Status[] value();
   }

}

校验规则

java 复制代码
package com.sofwin.validator.config;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;

/**
 * @packageName: com.sofwin.validator.config
 * @author: wentao
 * @date: 2023/9/4 22:04
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 自定义校验柜子
 */
public class StatusConstraint implements ConstraintValidator<Status,Integer> {

    List<String> statusType;

    /**
     * 一般进行初始化
     * @param constraintAnnotation
     */
    @Override
    public void initialize(Status constraintAnnotation) {
        String[] strings = constraintAnnotation.statusType();
        statusType = Arrays.asList(strings);
    }

    /**
     *
     * @param value  参数的值
     * @param context
     * @return  true 通过不抛出异常  fasle不通过抛出异常
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        if (value !=null) {
            if (!statusType.contains(String.valueOf(value))) {
                return  false;
            }
            return  true;
        }
        return false;
    }
}

测试

实体类

java 复制代码
package com.sofwin.validator.domain;

import com.sofwin.validator.config.InsertGroup;
import com.sofwin.validator.config.Status;
import com.sofwin.validator.config.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.springframework.web.bind.annotation.Mapping;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.util.List;

/**
 * @packageName: com.sofwin.validator.domain
 * @author: wentao
 * @date: 2023/9/4 21:17
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 测试
 */
@Data
public class UserR {

    @NotBlank(message = "名称不能为空")
    //进行分组
    @NotBlank(message = "名称不能为空(InsertGroup)",groups = InsertGroup.class)
    private String name;

    @NotNull(message = "年龄不能为空")
    @NotNull(message = "年龄不能为空(InsertGroup)",groups = {InsertGroup.class,UpdateGroup.class})
    @Range(min = 1,max = 200,message ="最小为{min}岁,最大为{max}岁" )
    private Integer age;

    @Status(statusType = {"1","2"})
    private Integer status;

    @Size(message ="编号长度为 [4-8] ", min = 4, max = 8)
    private String idNo;



}

controller

java 复制代码
  @PostMapping("/validPost")
    public BaseResult validPostTest(@Valid @RequestBody UserR user ) {
       return BaseResult.ok(user);
    }

正常传参

非正常传参

3.7 当出现类中出现嵌套的情况

当出现嵌套的情况只需要在类的属性中在加一个注解@Valid

实体类

java 复制代码
package com.sofwin.validator.domain;

import com.sofwin.validator.config.InsertGroup;
import com.sofwin.validator.config.Status;
import com.sofwin.validator.config.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.springframework.web.bind.annotation.Mapping;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.util.List;

/**
 * @packageName: com.sofwin.validator.domain
 * @author: wentao
 * @date: 2023/9/4 21:17
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 测试
 */
@Data
public class UserR {

    @NotBlank(message = "名称不能为空")
    //进行分组
    @NotBlank(message = "名称不能为空(InsertGroup)",groups = InsertGroup.class)
    private String name;

    @NotNull(message = "年龄不能为空")
    @NotNull(message = "年龄不能为空(InsertGroup)",groups = {InsertGroup.class,UpdateGroup.class})
    @Range(min = 1,max = 200,message ="最小为{min}岁,最大为{max}岁" )
    private Integer age;

    @Status(statusType = {"1","2"})
    private Integer status;

    @Size(message ="编号长度为 [4-8] ", min = 4, max = 8)
    private String idNo;


    //嵌套使用valid才能生效
    @Valid
    private List<SonUser> sonUserList;
}
java 复制代码
package com.sofwin.validator.domain;

import com.sofwin.validator.config.InsertGroup;
import com.sofwin.validator.config.UpdateGroup;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;

/**
 * @packageName: com.sofwin.validator.domain
 * @author: wentao
 * @date: 2023/9/4 21:17
 * @version: 1.0
 * @email 1660420659@qq.com
 * @description: 测试
 */
@Data
public class SonUser {

    @NotBlank(message = "sonName不能为空")
    private String sonName;

}

controller

java 复制代码
  @PostMapping("/validPost")
    public BaseResult validPostTest(@Valid @RequestBody UserR user ) {
       return BaseResult.ok(user);
    }

四、 总结

@Valid:没有分组的功能。

@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上

@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制

@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上

两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能

相关推荐
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
种树人202408194 小时前
如何在 Spring Boot 中启用定时任务
spring boot
Chrikk5 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*5 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue5 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man5 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟5 小时前
使用python向钉钉群聊发送消息
java·python·钉钉