Spring Validation的校验顺序问题及解决过程

 更新时间:2026年01月09日 09:19:23   作者:她又在丛中笑  
本文主要介绍了使用@GroupSequence注解来解决接口入参校验顺序不稳定的问题,并提供了具体的实现步骤和代码示例

问题场景

测试发现对同一个接口调用多次时,返回的校验异常信息不同,经过问题追踪,入参实体类代码如下:

@Data
public class EditDevNameDto {
    @NotBlank(message = "deviceSn must not null")
    private String deviceSn;
    @NotBlank(message = "deviceName must not null")
    private String deviceName;
}

当这个接口入参的deviceSn和deviceName均为空值时,调用多次的话会出现两个msg错误信息循环返回的问题。

原理剖析

怀疑是调用接口时,校验注解的先后顺序是不确定的,所以,可能deviceSn先被校验,可能deviceName先被校验,那要解决这个问题就要规定校验的顺序才行。

解决方法

使用@GroupSequence注解实现顺序的稳定性。

创建五个接口

public interface GroupA {
}

public interface GroupB {
}

public interface GroupC {
}

public interface GroupD {
}

@GroupSequence({GroupA.class,GroupB.class,GroupC.class,GroupD.class})
public interface Group {
}

修改Controller控制层代码

注意在入参中加入@Validated(Group.class)注解,其中要加入被@GroupSequence修饰的类对象。

    @PostMapping("/edit_device_name")
    public ExecuteResult editDevName(
    	@RequestBody @Validated(Group.class) EditDevNameDto dev) {
        // -----逻辑代码
        return null;
    }

修改实体类代码

在实体类中使用校验注解中添加groups属性,顺序按照@GroupSequence类规定的顺序即可。

@Data
public class EditDevNameDto {
    @NotBlank(message = "deviceSn must not null", groups = {GroupA.class})
    private String deviceSn;
    @NotBlank(message = "deviceName must not null", groups = {GroupB.class})
    private String deviceName;
}

整改结果

当这个接口入参的deviceSn和deviceName均为空值时,频繁调用依然是按照先校验deviceSn,后校验deviceName的顺序进行参数校验。

后续问题

问题原因

解决了上面的问题,过了几天,又出现了解决校验顺序的问题,但是这次又不同于上一次,这次的Dto参数接收类有些复杂,代码如下:

@Data
public class UpdateInfoDto {

    @NotBlank(message = "deviceSn can not be empty")
    @GBDeviceSnValid
    private String deviceSn;


    @Valid // 让CommonDto类中的校验属性生效
    @GBChannelDuplicateValid
    @CollectionNotEmptyValid(message = "channels cannot be empty")
    private List<CommonDto> channels;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonDto{

    @GBChannelIdValid
    private String id;

    @NotNull(message = "channelId must not be null")
    @PositiveOrZero(message = "channelId only Integer or zero")
    private Integer channelId;
}

以上代码中有两个类,共同组合成参数接收类,要纠正校验顺序混乱问题,还是需要用到一开始讲到的@GroupSequence注解,但是,有两个问题:

  1. Dto中还有一个对象类型的属性。
  2. 对象类型属性中的子属性(id、channelId)也要成功校验

解决方案

首先,为了让Dto中的对象类型的属性也能正常校验,需要添加@Valid注解;

然后,在对象类型属性中的子属性中也需要像文章前面所说的在校验注解中设置groups属性。

修改后的代码如下:

@Data
public class UpdateInfoDto {

    @NotBlank(message = "deviceSn can not be empty", groups = {GroupA.class})
    @GBDeviceSnValid(groups = {GroupA.class})
    private String deviceSn;


    @Valid // 让CommonDto类中的校验属性生效
    @GBChannelDuplicateValid(groups = {GroupB.class})
    @CollectionNotEmptyValid(message = "channels cannot be empty", groups = {GroupB.class})
    private List<CommonDto> channels;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonDto{

    @GBChannelIdValid(groups = {GroupC.class})
    private String id;

    @NotNull(message = "channelId must not be null", groups = {GroupD.class})
    @PositiveOrZero(message = "channelId only Integer or zero", groups = {GroupD.class})
    private Integer channelId;
}

如果CommonDto中校验属性的注解不设置groups,CommonDto中的校验属性就会失效,这是个大坑

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java实现学生信息录入界面

    java实现学生信息录入界面

    这篇文章主要为大家详细介绍了java实现学生信息录入界面,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • springboot 缓存@EnableCaching实例

    springboot 缓存@EnableCaching实例

    这篇文章主要介绍了springboot 缓存@EnableCaching实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Mybatis中使用万能的Map传参实现

    Mybatis中使用万能的Map传参实现

    在编程中,有可能遇到我们的实体类或者数据库中表的字段或参数过多的情况,那这时候用Map传参是比较理想的选择,本文就详细的介绍一下如何使用,感兴趣的可以了解下
    2021-07-07
  • 深入理解窗口令牌WindowToken

    深入理解窗口令牌WindowToken

    这篇文章主要介绍了窗口令牌WindowToken的概念与作用,它是对应用组件的行为进行规范管理的一个手段。WindowToken由应用组件或其管理者负责向WMS声明并持有
    2021-08-08
  • Java网络编程TCP实现聊天功能

    Java网络编程TCP实现聊天功能

    这篇文章主要为大家详细介绍了Java网络编程TCP实现聊天功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • 一文揭秘Java内存模型的隐匿陷阱与解决方案

    一文揭秘Java内存模型的隐匿陷阱与解决方案

    这篇文章主要为大家详细介绍了Java中内存模型的隐匿陷阱与解决方案,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-06-06
  • Java JUC并发集合详解之线程安全容器完全攻略

    Java JUC并发集合详解之线程安全容器完全攻略

    Java通过java.util.concurrent(JUC)包提供了一整套线程安全的并发容器,它们不仅是简单的同步包装,更是基于精妙并发算法构建的高性能工具,本文给大家介绍Java JUC并发集合详解之线程安全容器完全攻略,感兴趣的朋友一起看看吧
    2025-09-09
  • SpringBoot源码阅读之spring.factories的加载机制详解

    SpringBoot源码阅读之spring.factories的加载机制详解

    Spring Boot通过`spring.factories`文件实现自动装配,该文件位于`META-INF`目录下,Spring Boot在启动时会读取该文件并实例化其中配置的实现类
    2025-11-11
  • MyBatis延迟加载与多级缓存全解析

    MyBatis延迟加载与多级缓存全解析

    文章介绍MyBatis的延迟加载与多级缓存机制,延迟加载按需加载关联数据提升性能,一级缓存会话级默认开启,二级缓存工厂级支持跨会话共享,增删改操作会清空对应缓存,确保数据一致性,感兴趣的朋友跟随小编一起看看吧
    2025-09-09
  • Java中如何编写一个数的n次方(幂运算)?

    Java中如何编写一个数的n次方(幂运算)?

    本文介绍了使用pow函数和自定义for循环计算幂的O(n)时间复杂度方法,然后重点讲解了快速幂算法的分治思想,以及从二进制角度的解释,包括如何通过位运算和循环迭代实现高效计算,给出了Java代码实现
    2024-07-07

最新评论