开发者

JAVA校验之@Valid和@Validated实践指南

目录
  • 概述
    • 区别
    • 校验类型
  • 实践
    • 分组校验
    • 创建分组
    • 设置实体
    • 使用校验
    • 嵌套验证
    • 验证嵌套属性
    • 验证集合
    • 异常处理
  • 引用

    概述

    区别

    • 来源 (规范与框架绑定)    - @Validated: 它是 Spring 框架特有的注解,属于 Spring 自身提供的一个功能增强。它基于 jsR 303 (Bean Validation 1.0) / JSR 380 (Bean Validation 2.0) 规范,但在其基础上进行了扩展,特别适用于 Spring 环境。

          - @Valid: 这是一个标准的 Java EE 注解,是 JSR 303 / JSR 380 (Bean Validation) 规范的核心组成部分。它不依赖于任何特定的框架,可以在任何支持 Bean Validation 的环境中(如 Hijavascriptbernate Validator、CDI 等)使用。

    • 注解位置 (适用范围)    - @Validated: 主要用于(声明在类上时,会对其所有公共方法参数进行校验)、方法方法参数。它不能直接用于成员属性(字段)。当用于类上时,通常与 Spring 的 AOP 拦截结合使用。

          - @Valid: 可以用于方法构造函数方法参数以及成员属性(字段)。当用于成员属性时,它会触发对该属性所引用对象的级联校验。

    • 分组 (灵活性)    - @Validated支持分组验证。这是其相比 @Valid 的一个重要优势。你可以通过在 @Validated 注解中指定一个或多个接口类来定义校验组,从而在不同的业务场景(如创建、更新)中应用不同的校验规则。

          - @Valid: 仅支持标准的 Bean Validation 功能,不支持分组验证。当它触发校验时,它会执行目标对象上所有未指定分组的(即默认组Default.class)以及任何被显式指定为 Default.class 的约束。

    • 嵌套验证 (递归校验)    - @Validated: 单独使用时,@Validated 本身不支持嵌套验证。它通常需要与 @Valid 配合使用才能实现对复杂对象内部嵌套属性的校验。@Validated 主要负责激活校验器并指定校验组,而 @Valid 则负责递归遍历对象图。

          - @Valid支持嵌套验证。当一个对象的属性本身也是一个需要被校验的对象时,你可以在该属性上添加 @Valid,这样在父对象被校验时,其内部的嵌套属性也会被自动递归校验。

    校验类型

    • @NotNull: 被注解的元素不能为 null

          适用类型: 任何类型(对象、基本类型包装类、集合、数组等)。

    • @NotEmpty: 被注解的元素不能为 null 且其大小/长度必须大于零。

          适用类型CharSequence (字符串)、Collection (集合如 ListSet)、MapArray (数组)。

    • @NotBlank: 被注解的 String 类型不能为 null,不能是空字符串 (""),且不能只包含空白字符(如空格、制表符)。

          适用类型CharSequence (字符串)。

    • @Min(value): 被注解的元素必须是一个数字,且其值必须大于或等于指定值 value

          适用类型BigDecimalBigIntegerByteShortIntegerLongFloatDouble 及其包装类。

    • @Max(value): 被注解的元素必须是一个数字,且其值必须小于或等于指定值 value

          适用类型BigDecimalBigIntegerByteShortIntegerLongFloatDouble 及其包装类。

    • @DecimalMin(value, inclusive=true): 被注解的元素必须是一个数字,且其值必须大于或等于指定值 value(默认 inclusive=true)。如果 inclusive=false,则表示大于 value

          适用类型BigDecimalBigIntegerByteShortIntegerLongFloatDouble 及其包装类,以及 String (其内容必须是数字)。

    • @DecimalMax(value, inclusive=true): 被注解的元素必须是一个数字,且其值必须小于或等于指定值 value(默认 inclusive=true)。如果 inclusive=false,则表示小于 value

          适用类型BigDecimalBigIntegerByteShortIntegerLongFloatDouble 及其包装类,以及 String (其内容必须是数字)。

    • @Positive: 被注解的元素必须是正数(大于 0)。

          适用类型BigDecimalBigIntegerByteShortIntegerLongFloatDouble 及其包装类。

    • @PositiveOrZero: 被注解的元素必须是正数或零(大于等于 0)。

          适用类型BigDecimalBigIntegerByteShortIntegerLongFloatDouble 及其包装类.

    • @Negative: 被注解的元素必须是负数(小于 0)。

          适用类型BigDecimalBigIntegerByteShortIntegerLongFloatDouble 及其包装类.

    • @NegativeOrZero: 被注解的元素必须是负数或零(小于等于 0)。

          适用类型BigDecimalBigIntegerByteShortIntegerLongFloatDouble 及其包装类.

    • @Size(min, max): 被注解的元素大小/长度必须在指定范围(min 到 max)之内。

          适用类型CharSequence (字符串)、Collection (集合)、MapArray (数组)。

    • @Digits(integer, fraction): 被注解的元素必须是一个数字,其整数部分的位数不能超过 integer 位,小数部分的位数不能超过 fraction 位。

          适用类型BigDecimalBigIntegerByteShortIntegerLongFloatDouble 及其包装类,以及 String (其内容必须是数字)。

    • @Past: 被注解的日期或时间必须是一个过去的日期或时间。

          适用类型java.util.Datejava.util.Calendarjava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTimejava.time.MonthDayjava.time.OffsetDateTimejava.time.OffsetTimejava.time.Yearjava.time.YearMonthjava.time.ZonedDateTime 等。

    • @PastOrPresent: 被注解的日期或时间必须是一个过去的或当前的日期或时间。

          适用类型: 同 @Past

    • @Future: 被注解的日期或时间必须是一个未来的日期或时间。

          适用类型: 同 @Past

    • @FutureOrPresent: 被注解的日期或时间必须是一个未来的或当前的日期或时间。

       编程客栈;   适用类型: 同 @Past

    • @Pattern(regexp, flags=Pattern.CASE_INSENSITIVE): 被注解的 String 必须符合指定的正则表达式 regexp

          适用类型CharSequence (字符串)。

    • @Email: 被注解的 String 必须是有效的邮件地址格式。

          适用类型CharSequence (字符串)。

    • @AssertTrue: 被注解的布尔类型必须为 true。通常用于验证复杂逻辑(如两个字段间的关联验证),需要一个返回 boolean 的 getter 方法。

          适用类型Boolean 或 boolean

    • @AssertFalse: 被注解的布尔类型必须为 false

          适用类型Boolean 或 boolean

    实践

    分组校验

    分组校验允许你根据不同的业务场景(如创建、更新、删除等)对同一个实体类应用不同的验证规则,从而实现更灵活和精细的控制。

    特别注意

    • 定义分组必须使用接口。 这些接口是空的,仅作为校验规则的标识。
    • 要校验的字段上的注解必须通过 groups 属性指定所属的分组。 如果一个校验注解没有指定 groups,它将默认属于 Default.class 组,并在任何未指定分组或显式指定 Default.class 的校验中生效。
    • 在 Controller 方法的 @Validated 注解中指定要激活的分组。

    创建分组

    // 用于创建操作的校验组
    public interface CreationGroup { }
    
    // 用于更新操作的校验组
    public interface UpdateGroup { }
    
    // 你还可以定义其他分组,例如:
    // public interface DeleteGroup { }
    

    设置实体

    在实体类的字段上添加校验注解,并使用 groups 属性指定该校验规则属于哪个分组。一个字段可以属于多个分组。

    @Data
    public class UserBean {
    
        // 创建和更新时都校验用户名非空
        // 默认的 Default 组也适用,但如果指定了分组,则只在指定分组下生效
        @NotEmpty(message = "用户名不能为空", groups = {CreationGroup.class, UpdateGroup.class})
        private String username;
    
        // 年龄校验,无分组,默认在所有场景(包括未指定分组的 @Validated)下都校验
        @Min(value = 18, message = "年龄不能小于18岁")
        private Integer age;
    
        // 邮箱格式校验,无分组,默认在所有场景下都校验
        @Email(message = "邮箱格式不正确")
        private String email;
    
        // ID 校验,只在更新时生效,且必须大于等于1
        @NotNull(message = "ID不能为空", groups = UpdateGroup.class) // 更新时ID不能为null
        @Min(value = 1, message = "ID必须大于0", groups = {UpdateGroup.class})
        private Long id;
    
        // ... 其他属性
    }
    

    使用校验

    在 Controller 方法的参数上使用 @Validated 注解,并传入需要激活的校验组。

    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    
    import javax.validation.Valid; // 导入 javax.validation.Valid
    
    @RestController
    @RequestMapping("validation")
    public class ValidationController {
    
        /**
         * 更新用户接口:只激活 UpdateGroup 组的校验规则。
         * 此时,UserBean 中的 id 字段会根据 UpdateGroup 的规则进行校验。
         * username 和 age、email 字段则会根据它们自身定义的(或默认的)规则进行校验。
         */
        @GetMapping("updateUser")
        public UserBean updateUser(@Validated({UpdateGroup.class}) UserBean userBean){
            // 如果校验失败,会抛出 MethodArgumentNotValidException,由全局异常处理器捕获
            System.out.println("更新用户成功: " + userBean);
            return userBean;
        }
    
        /**
         * 创建用户接口:只激活 CreationGroup 组的校验规则。
         * 此时,UserBean 中的 username 字段会根据 CreationGroup 的规则进行校验。
         * age 和 email 字段则会根据它们自身定义的(或默认的)规则进行校验。
         * id 字段由于不属于 CreationGroup,将不会被校验。
         */
        @GetMapping("createUser")
        public UserBean createUser(@Validated({CreationGroup.class}) UserBean userBean){
            // 如果校验失败,会抛出 MethodArgumentNotValidException,由全局异常处理器捕获
            System.out.println("创建用户成功: " + userBean);
            return userBean;
        }
    
        // 注意:如果一个方法参数不加 @Validated,但实体类中有 @Valid 注解,
        // 则会默认校验所有无分组javascript的字段(即 Default 组)。
        // 例如:
        // @PostMapping("defaultValid")
        // public UserBean defaultValid(@Valid @RequestBody UserBean userBean) {
        //     return userBean;
        // }
    }
    

    嵌套验证

    嵌套校验(Nested Validation) 指的是在验证外部对象时,对其内部包含的其他对象进行递归验证的过程。当一个对象中包含另一个对象作为属性,并且需要对这个被包含的对象也进行验证时,就需要进行嵌套校验。

    嵌套属性指的是在一个对象中包含另一个对象作为其属性的情况。换句话说,当一个对象的属性本身又是一个对象(非基本类型),那么这些被包含的对象就可以称为嵌套属性

    特别提示

    • 想要嵌套校验生效,必须在嵌套属性上加 @Valid 注解。这个注解告诉校验器深入到这个属性对应的对象内部进行校验。
    • @Valid 注解默认校验无分组的项。它会触发嵌套对象上所有属于 Default.class 组的校验。
    • 嵌套验证要支持分组,需要结合 @Validated({YourGroup.class})。 当 @Validated 指定了分组并作用于包含 @Valid 属性的外部对象时,@Valid 会将这个分组信息传递给嵌套对象,从而使其内部只有对应分组的校验规则生效。

    验证嵌套属性

    首先,定义你的嵌套实体类,并为其字段添加校验规则:

    import lombok.Data;
    import javax.validation.constraints.NotBlank;
    
    @Data
    public class AddressBean {
        @NotBlank(message = "国家不能为空")
        private String country;
        @NotBlank(message = "城市不能为空")
        private String city;
    }
    

    接着,在主实体类中包含这个嵌套属性,并在该属性上添加 @Valid 和 @NotNull(如果地址对象本身不能为 npythonull):

    @Data
    public class UserBean {
    
        @NotEmpty(message = "用户名不能为空", groups = {CreationGroup.class})
        private String username;
    
        @Min(value = 18, message = "年龄不能小于18岁")
        private Integer age;
    
        @Email(message = "邮箱格式不正确")
        private String email;
    
        // 嵌套验证必须要加上 @Valid
        // 同时可以添加 @NotNull 来确保 address 对象本身不为空
        @Valid
        @NotNull(message = "地址信息不能为空")
        private AddressBean address; // 嵌套属性
    
        // ... 其他属性和 getter/settandroider
    }
    

    然后在 Controller 中使用 @Validated 来触发校验:

    @RestController
    @RequestMapping("validation")
    public class ValidationController {
    
        /**
         * 嵌套验证示例。
         * 当 @RequestBody UserBean userBean 传入时,
         * 如果 userBean 内部的 address 属性有 @Valid 注解,
         * 那么 address 对象自身的校验规则也会被触发。
         * 此处如果 address 为 null 或其内部字段(country, city)为空,都会触发校验失败。
         */
        @PostMapping("nestValid")
        public UserBean nestValid(@Validated @RequestBody UserBean userBean){ // @Validated 也可以不指定分组,则校验 Default 组
            System.out.println("嵌套验证成功: " + userBean);
            return userBean;
        }
    
        /**
         * 带分组的嵌套验证示例。
         * 此时,只有 UserBean 中属于 CreationGroup 的校验规则生效,
         * 并且这个分组也会传递给嵌套的 AddressBean,如果 AddressBean 中也有分组校验规则,它们就会生效。
         * 此外,因为 address 属性是 @Valid 且 @NotNull,它会始终被校验。
         */
        @PostMapping("nestValidwithGroup")
        public UserBean nestValidWithGroup(@Validated(CreationGroup.class) @RequestBody UserBean userBean){
            System.out.println("带分组的嵌套验证成功: " + userBean);
            return userBean;
        }
    }
    

    验证集合

    当请求体是一个对象的集合(如 List<YourBean>)时,校验的原理与嵌套验证类似,你需要将 @Valid 放在集合参数前,而 @Validated 则用于指定校验组(如果需要)。同时,你也可以对集合本身进行非空校验。

    @RestController
    @RequestMapping("/metadataTemplate")
    public class ReMetadataTemplateController {
    
        // 假设 reMetadataTemplateService 已注入
    
        /**
         * 更改元数据模板状态接口,支持对列表本身非空及列表中每个对象的字段进行分组校验。
         *
         * @param list 待更新的元数据模板列表
         * @return 结果
         */
        @PutMapping("/changeStatus")
        @Validated(EditGroup.class) // 指定激活 EditGroup 组的校验
        public Void changeStatus(
            // @Valid 触发对 List 中每个 ReMetadataTemplateBo 对象的校验
            // @NotEmpty(message = "参数不能为空") 校验 List 本身不能为 null 或空集合
            @Valid @NotEmpty(message = "元数据模板列表不能为空", groups = EditGroup.class)
            @RequestBody List<ReMetadataTemplateBo> list
        ) {
            // 如果校验失败,会抛出 MethodArgumentNotValidException,由全局异常处理器捕获
            // ... 业务逻辑
            reMetadataTemplateService.changeStatus(list);
        }
    }
    

    异常处理

    为了更好地处理校验失败时抛出的异常,通常需要配置一个全局异常处理器

    引用

    • https://cloud.tencent.com/developer/article/2396081
    • Spring Boot Validation with @Valid and @Validated
    • JSR 303: Bean Validation
    • JSR 380: Bean Validation 2.0

    到此这篇关于JAVA校验之@Valid和@Validated实践指南的文章就介绍到这了,更多相关JAVA校验@Valid和@Validated内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜