Springboot参数校验@Validated和@Valid区别

本文详细比较了Springboot中@Validated和@Valid的使用场景,包括标准JSR-303验证与Spring扩展功能,以及如何进行嵌套校验和分组校验,通过实例演示了它们在验证PeopleDTO和AddressDTO中的应用。

@Validated和@Valid区别

上一篇讲了springboot自定义参数校验规则,本篇我们讲解@Validated和@Valid区别。Springboot中参数的校验我们可以使用@Validated和@Valid两个注解,这两个注解有什么区别?那种情况下使用@Validated注解?那种情况下使用@Valid注解?

带着这几个疑问我们先看看这两个注解的源码:

Validated源码如下:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {

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

}

@Valid源码如下:

@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface Valid {
}

Java中@Target注解的作用目标如下:

注解描述
ElementType.TYPE接口、类、枚举
ElementType.FIELD字段、枚举的常量
ElementType.METHOD方法
ElementType.PARAMETER方法参数
ElementType.CONSTRUCTOR构造函数
ElementType.LOCAL_VARIABLE局部变量
ElementType.ANNOTATION_TYPE注解
ElementType.PACKAGE

通过查看@Target属性,可知:

Valid可以用在:方法,字段、枚举的常量,构造函数,方法参数

Validated可以用在:接口、类、枚举、注解,方法,方法参数

通过上面的对比我们可以看出valid 可以作用在 字段、枚举的常量 上面,而Validated 不可以,所以嵌套校验需要使用 Valid。Validated 多了一个参数用于分组校验,所以如果需要分组校验需要使用Validated。

综上所诉,两者的区别如下:

@Valid:标准JSR-303规范的标记型注解,用来标记验证属性和方法返回值,进行级联和递归校验
@ValidatedSpring的注解,是标准JSR-303的一个变种(补充),提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制

Valid嵌套校验使用方式

比如我们定义了一个PeopleDTO,该DTO下的地址信息是一个对象,我们对地址信息也需要进行校验,校验方式如下:

我们在AddressDTO上面加上了 @Valid注解就可以实现嵌套校验了

package com.validator.demo.api.model.dto;

import java.io.Serializable;

import javax.validation.Valid;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.NotBlank;

public class PeopleDTO implements Serializable {

    private static final long serialVersionUID = 7515422823626784776L;
    @NotBlank(message = "姓名不能为空")
    private String            name;
    @NotBlank(message = "性别不能为空")
    private String            sex;
    @NotBlank(message = "生日不能为空")
    @Pattern(regexp = "[0-9]{4}-[0-9]{2}-[0-9]{2}", message = "生日输入数据异常,请确认!")
    private String            birthday;
    @Valid
    private AddressDTO        address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    public AddressDTO getAddress() {
        return address;
    }

    public void setAddress(AddressDTO address) {
        this.address = address;
    }

    public static long getSerialversionuid() {
        return serialVersionUID;
    }

}

AddressDTO源码如下:

package com.validator.demo.api.model.dto;

import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotBlank;

public class AddressDTO {
    @NotBlank(message = "地址不能为空!")
    @Size(min = 6, message = "地址不能小于六个字符,请确认!")
    private String addr;
    @NotBlank(message = "邮箱不能为空!")
    private String email;

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

}

Controller类的代码如下:

package com.validator.demo.api.controller;

import javax.validation.Valid;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.validator.demo.api.base.DateResult;
import com.validator.demo.api.model.dto.PeopleDTO;
import com.validator.demo.api.model.vo.PeopleVO;

@RestController
@RequestMapping("/test/api")
public class ValidatorController {
    @PostMapping("/validatortest")
    public DateResult<PeopleVO> test(@RequestBody @Valid PeopleDTO peopleDTO) {
        DateResult<PeopleVO> dateResult = new DateResult<PeopleVO>();

        //具体的业务逻辑
        //省略
        return dateResult;
    }

}

PostMan运行测试结果如下:

Validated 分组校验使用方式

在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。比如PeopleDTO中新增的时候姓名不可以为空,修改的时候性别不可以为空,生日新增和修改都不可以为空。分组校验实现方式如下:

我们先定义两个分组类,一个是AddGroup,一个是UpdateGroup,源码如下:

package com.validator.demo.api.validation;

public abstract interface AddGroup {
}
package com.validator.demo.api.validation;

public abstract interface UpdateGroup {
}

PeopleDTO代码如下:

package com.validator.demo.api.model.dto;

import java.io.Serializable;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.NotBlank;

import com.validator.demo.api.validation.AddGroup;
import com.validator.demo.api.validation.UpdateGroup;

public class PeopleDTO implements Serializable {
    private static final long serialVersionUID = 7515422823626784776L;
    @NotBlank(groups = { AddGroup.class }, message = "姓名不能为空")
    private String            name;
    @NotBlank(groups = { UpdateGroup.class }, message = "性别不能为空")
    private String            sex;
    @NotNull(groups = { AddGroup.class, UpdateGroup.class }, message = "生日不能为空")
    @Pattern(regexp = "[0-9]{4}-[0-9]{2}-[0-9]{2}", message = "生日输入数据异常,请确认!")
    private String            birthday;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    public static long getSerialversionuid() {
        return serialVersionUID;
    }

}

在Controller中我们定义两个方法,一个是新增,一个是修改。在参数的前面添加@Validated注解,并添加相应的分组,代码如下:

package com.validator.demo.api.controller;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.validator.demo.api.base.DateResult;
import com.validator.demo.api.model.dto.PeopleDTO;
import com.validator.demo.api.model.vo.PeopleVO;
import com.validator.demo.api.validation.AddGroup;
import com.validator.demo.api.validation.UpdateGroup;

@RestController
@RequestMapping("/test/api")
public class ValidatorController {
    @PostMapping("/validatoradd")
    public DateResult<PeopleVO> add(@RequestBody @Validated(AddGroup.class) PeopleDTO peopleDTO) {
        DateResult<PeopleVO> dateResult = new DateResult<PeopleVO>();

        //具体的业务逻辑
        //省略
        return dateResult;
    }

    @PostMapping("/validatorupdate")
    public DateResult<PeopleVO> update(@RequestBody @Validated(UpdateGroup.class) PeopleDTO peopleDTO) {
        DateResult<PeopleVO> dateResult = new DateResult<PeopleVO>();

        //具体的业务逻辑
        //省略
        return dateResult;
    }
}

使用postman调用验证一下校验。我们的逻辑为新增的时候姓名不可以为空,修改的时候性别不可以为空,生日新增和修改都不可以为空。我们先调用新增方法,不传递姓名,返回结果如下:

然后调用修改方法,不传递性别,返回结果如下:

​测试源码,点击链接下载

<think>我们正在讨论SpringBoot中使用@Validated注解对List<对象>类型的参数进行校验的问题。根据提供的引用,我们可以总结出:-引用[1]说明了SpringBoot使用@Validated需要引入spring-boot-starter-validation依赖。-引用[2]展示了如何在一个对象中嵌入另一个对象进行校验(使用@Valid)。-引用[3]展示了在Controller方法参数上使用@Validated注解,并且指定分组校验。用户的问题:如何对List<对象>类型的参数进行校验?我们知道,在SpringBoot中,对于单个对象的校验,我们直接在对象前加上@Valid@Validated即可。但是,对于List<对象>,我们需要额外的处理。解决方案:1.使用一个包装类,将List<对象>作为该包装类的一个属性,然后对这个属性标注@Valid(或@Validated)注解。这样,Spring在绑定数据时会对List中的每个对象进行校验。2.使用自定义的校验注解校验器。这里我们采用第一种方式,因为它更符合SpringBoot校验机制,并且与引用[2]中嵌入校验的方式一致。步骤:①创建包装类,将List<对象>作为属性,并在该属性上添加@Valid注解(注意:不能直接使用@Validated在这里,@Validated是用在类方法上的,而@Valid是用在字段、方法参数或方法返回值的)。②在Controller方法的参数上使用@Validated注解(如果使用分组校验则需要,否则可以直接用@Valid)来触发校验。注意:SpringBoot在2.3版本之后,需要显式添加依赖spring-boot-starter-validation(如引用[1]所示)。下面通过一个例子说明:假设我们有一个StudentDTO类,其中有一些校验注解:```javapublicclassStudentDTO{@NotBlank(message="学生姓名不能为空")privateStringname;@Min(value=18,message="年龄必须大于18岁")privateintage;//省略gettersetter}```然后,我们创建一个包装类,用于封装List<StudentDTO>:```javapublicclassStudentListDTO{@ValidprivateList<StudentDTO>students;//必须提供settergetterpublicList<StudentDTO>getStudents(){returnstudents;}publicvoidsetStudents(List<StudentDTO>students){this.students=students;}}```最后,在Controller中,我们接收这个包装类,并在参数前加上@Valid@Validated(注意:这里不能直接使用@Validated校验包装类内部的List,但可以在参数上使用@Validated来触发校验,不过更常见的做法是使用@Valid):```java@RestController@RequestMapping("/students")publicclassStudentController{@PostMapping("/batch")publicResultaddBatch(@RequestBody@ValidStudentListDTOstudentList){//处理批量新增学生returnResult.ok();}}```这样,当请求中的List<StudentDTO>被传入时,Spring会为StudentListDTO中的students字段(因为标注了@Valid)进行校验,从而校验List中每一个StudentDTO对象。但是,如果不想使用包装类,可以直接在Controller方法的参数上使用@Validated(在类上)配合@Valid在List<StudentDTO>参数前。然而,根据Spring的文档,对于直接是List类型的参数,我们需要在参数前同时使用@Valid@Validated(在类上)?实际上,SpringBoot默认不支持直接对List<对象>进行校验,因为它是一个集合,而校验注解不能直接写在集合类型上。所以,使用包装类是最直接的方法。另一种方案:在Controller方法上使用@Validated(类级别),然后在参数前使用@Valid(针对包装类)或者使用@Valid注解在List上?然而,当我们尝试在List参数前直接使用@Valid时,Spring并不会递归校验其中的对象(除非我们使用@Valid在包装类的字段上)。总结:为了对List<对象>进行校验,我们需要将List放入一个包装类中,并对该字段使用@Valid注解,然后在Controller方法的参数上使用@Valid@Validated(分组校验时)。另外,引用[3]提到了分组校验。如果我们需要在List中的对象上使用分组校验,那么可以在@Valid(在包装类字段上)后无法指定分组,因此需要改为在包装类字段上使用@Validated(分组.class)?但注意,@Valid不支持分组,而@Validated支持分组。所以我们可以这样调整:修改包装类:```javapublicclassStudentListDTO{@Validated(Insert.class)//但是,@Validated不能用在字段上,只能用在类型方法上?所以这里不行!privateList<StudentDTO>students;//...}```这样写是错误的,因为@Validated注解不能用在字段上。因此,如果我们想对List中的对象进行分组校验,我们可以将@Validated注解放在Controller类上,然后在参数前使用@Validated(分组.class)?但是,这样会作用于整个类,而且参数上的@Validated只能用于参数上(它也可以指定分组)。然而,在参数上使用@Validated(分组.class)配合包装类字段上的@Valid是不行的,因为分组信息不会传递下去。正确的做法是在包装类的字段上使用@Valid(不支持分组),然后在StudentDTO的校验注解上指定分组(这样如果没有激活该分组,则不会校验)。但如果我们激活了分组,那么在字段上使用@Valid会触发所有未指定分组的约束以及指定了当前激活分组的约束。因此,我们可以:在Controller方法参数上使用@Validated(分组.class),并且参数是包装类(包装类的字段上使用@Valid):```java@RestController@RequestMapping("/students")@Validated//如果整个Controller需要分组校验,可以在类上添加@Validated,但这里我们不需要,因为我们要在方法参数上指定分组publicclassStudentController{@PostMapping("/batch")publicResultaddBatch(@RequestBody@Validated(Insert.class)StudentListDTOstudentList){//处理批量新增学生returnResult.ok();}}```然后,在StudentListDTO中:```javapublicclassStudentListDTO{@Valid//这里会激活StudentDTO中所有默认的约束(没有分组的)与当前激活分组(Insert)匹配的约束privateList<StudentDTO>students;//...}```在StudentDTO中,我们需要在约束注解上指定分组(例如Insert):```javapublicclassStudentDTO{@NotBlank(message="学生姓名不能为空",groups=Insert.class)//指定Insert分组privateStringname;@Min(value=18,message="年龄必须大于18岁",groups=Insert.class)//指定Insert分组privateintage;//省略gettersetter}```注意:如果约束注解上指定了分组(例如Insert.class),那么当使用@Valid进行校验时,需要校验器被激活相应的分组(例如在Controller方法的参数上使用@Validated(Insert.class))。在这种情况下,包装类字段上的@Valid会传递这个分组吗?实际上,分组校验不会通过@Valid传递。在Spring校验中,在方法参数上使用的@Validated(Insert.class)会激活整个对象图的Insert分组校验。也就是说,它会递归校验所有满足Insert分组的约束。因此,在包装类字段上的@Valid虽然没有指定分组,但会被传递当前激活的分组(即Insert分组)。所以,这种方式是可行的。结论:在SpringBoot中,对List<对象>进行校验的可行方法是将其包装在一个包装类中,然后在包装类的该字段上添加@Valid注解(不需要分组)或@Validated注解(但@Validated不能用在字段上,所以用@Valid),然后在Controller方法的参数上使用@Validated并指定分组(如果需要分组校验)。这样,Spring就会递归校验List中的每个对象。另外,如果不想使用包装类,可以使用自定义校验注解配合校验器,但这需要编写更多代码。因此,使用包装类是最简便的方法。生成相关问题:1.如何在分组校验中使用List<对象>?2.如果不使用包装类,如何实现对List<对象>的校验?3.在SpringBoot中,@Valid@Validated有什么区别?4.分组校验的继承关系如何处理?注意:在以上方案中,我们使用的是SpringBoot默认的校验框架(HibernateValidator),所以需要确保引入了正确的依赖。下面,我们按照用户的要求总结回答:在SpringBoot中,使用@Validated注解对List<对象>类型的参数进行校验,不能直接在List<对象>参数前使用@Validated@Valid。但是,我们可以通过将List<对象>封装在一个包装类中,然后对包装类中代表List的字段添加@Valid注解(也可以结合分组校验)。然后在Controller方法参数上使用@Validated(需要分组时指定分组)或者@Valid(无分组)来触发校验。示例代码:依赖(如引用[1]):```xml<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId><version>2.7.4</version></dependency>```包装类:```javapublicclassStudentListDTO{@Valid//这里触发List中每个对象的校验privateList<StudentDTO>students;//getterandsetter}```控制器:```java@RestControllerpublicclassStudentController{@PostMapping("/batchAdd")publicStringbatchAddStudents(@RequestBody@ValidStudentListDTOstudentListDTO){//业务处理return"success";}}```如果需要分组校验,则:```java@PostMapping("/batchAdd")publicStringbatchAddStudents(@RequestBody@Validated(Insert.class)StudentListDTOstudentListDTO){//业务处理return"success";}```同时,在StudentDTO类的字段上指定分组(例如Insert.class)。这样,SpringBoot就会对List中的每个对象进行校验。注意:如果List中的某个对象校验失败,会抛出MethodArgumentNotValidException异常,我们可以通过全局异常处理器捕获并处理。可行性:完全可行。</think> 在 Spring Boot 中使用 `@Validated` 注解对 `List<对象>` 类型的参数进行校验是**完全可行的**,但需要结合 `@Valid` 注解实现。以下是具体方法实现步骤: --- ### ✅ 使用方法(需结合 `@Valid` 注解) #### 步骤 1:创建包裹 List 的包装类 ```java public class StudentListRequest { @Valid // 关键注解:触发 List 内对象的校验规则 private List<StudentDTO> students; // 必须添加 getter/setter public List<StudentDTO> getStudents() { return students; } public void setStudents(List<StudentDTO> students) { this.students = students; } } ``` #### 步骤 2:在 DTO 对象中定义校验规则 ```java public class StudentDTO { @NotBlank(message = "姓名不能为空") private String name; @Min(value = 18, message = "年龄必须≥18岁") private Integer age; // getter/setter } ``` #### 步骤 3:在 Controller 中使用 `@Validated` ```java @PostMapping("/batch/add") public ResponseEntity<?> batchAddStudents( @RequestBody @Validated StudentListRequest request // 校验包裹类 ) { // 业务逻辑 return ResponseEntity.ok("校验成功"); } ``` --- ### ⚡ 校验触发流程 ```mermaid graph TD A[HTTP 请求] --> B[JSON 解析为 StudentListRequest] B --> C{Spring 校验流程} C -->|扫描包裹类| D[@Valid 触发校验] D --> E[遍历校验 List<StudentDTO>] E --> F[应用 StudentDTO 的 @NotBlank/@Min 等规则] F --> G[校验失败抛出 MethodArgumentNotValidException] ``` --- ### 🔍 关键点说明 1. **`@Valid` 与 `@Validated` 分工**: - `@Valid`(JSR-303 标准):用于**字段级**递归校验(必须加在 List 字段上) - `@Validated`(Spring 扩展):用于**类/方法参数级**校验(加在 Controller 参数上) 2. **校验规则继承**: - List 中的对象(如 `StudentDTO`)支持所有标准校验注解: ```java @Email, @Size, @Pattern, @NotNull // 等 ``` - 支持**分组校验**(如 `@Validated(Insert.class)`),需在包裹类参数指定分组 3. **错误处理**: - 校验失败自动返回 400 状态码 - 错误信息结构示例: ```json { "timestamp": "2023-05-01T12:00:00", "status": 400, "error": "Bad Request", "errors": [ { "field": "students[0].name", "message": "姓名不能为空" }, { "field": "students[2].age", "message": "年龄必须≥18岁" } ] } ``` --- ### ⚠️ 注意事项 1. **不支持直接校验裸 List 参数**: ```java // 错误写法:不会触发校验! @PostMapping("/error-example") public void errorExample(@RequestBody @Validated List<StudentDTO> list) {} ``` 2. **必须引入依赖**: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> ``` 3. **嵌套校验**: - 如需校验多层嵌套对象(如 `StudentDTO` 中包含 `AddressDTO`),需在嵌套对象字段也添加 `@Valid` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值