若栀 發表於 2026-1-9 09:19:23

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

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>问题场景</li><li>原理剖析</li><li>解决方法</li><ul class="second_class_ul"><li>创建五个接口</li><li>修改Controller控制层代码</li><li>修改实体类代码</li></ul><li>整改结果</li><ul class="second_class_ul"></ul><li>后续问题</li><ul class="second_class_ul"><li>问题原因</li><li>解决方案</li></ul><li>总结</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>问题场景</h2>
<p>测试发现对同一个接口调用多次时,返回的校验异常信息不同,经过问题追踪,入参实体类代码如下:</p>
<div class="jb51code"><pre class="brush:java;">@Data
public class EditDevNameDto {
    @NotBlank(message = "deviceSn must not null")
    private String deviceSn;
    @NotBlank(message = "deviceName must not null")
    private String deviceName;
}
</pre></div>
<p>当这个接口入参的deviceSn和deviceName均为空值时,调用多次的话会出现两个msg错误信息循环返回的问题。</p>
<p class="maodian"></p><h2>原理剖析</h2>
<p>怀疑是调用接口时,校验注解的先后顺序是不确定的,所以,可能deviceSn先被校验,可能deviceName先被校验,那要解决这个问题就要规定校验的顺序才行。</p>
<p class="maodian"></p><h2>解决方法</h2>
<p>使用@GroupSequence注解实现顺序的稳定性。</p>
<p class="maodian"></p><h3>创建五个接口</h3>
<div class="jb51code"><pre class="brush:java;">public interface GroupA {
}

public interface GroupB {
}

public interface GroupC {
}

public interface GroupD {
}

@GroupSequence({GroupA.class,GroupB.class,GroupC.class,GroupD.class})
public interface Group {
}
</pre></div>
<p class="maodian"></p><h3>修改Controller控制层代码</h3>
<p>注意在入参中加入@Validated(Group.class)注解,其中要加入被@GroupSequence修饰的类对象。</p>
<div class="jb51code"><pre class="brush:java;">    @PostMapping("/edit_device_name")
    public ExecuteResult editDevName(
            @RequestBody @Validated(Group.class) EditDevNameDto dev) {
      // -----逻辑代码
      return null;
    }
</pre></div>
<p class="maodian"></p><h3>修改实体类代码</h3>
<p>在实体类中使用校验注解中添加groups属性,顺序按照@GroupSequence类规定的顺序即可。</p>
<div class="jb51code"><pre class="brush:java;">@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;
}
</pre></div>
<p class="maodian"></p><h2>整改结果</h2>
<p>当这个接口入参的deviceSn和deviceName均为空值时,频繁调用依然是按照先校验deviceSn,后校验deviceName的顺序进行参数校验。</p>
<p class="maodian"></p><h2>后续问题</h2>
<p class="maodian"></p><h3>问题原因</h3>
<p>解决了上面的问题,过了几天,又出现了解决校验顺序的问题,但是这次又不同于上一次,这次的Dto参数接收类有些复杂,代码如下:</p>
<div class="jb51code"><pre class="brush:java;">@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&lt;CommonDto&gt; 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;
}
</pre></div>
<p>以上代码中有两个类,共同组合成参数接收类,要纠正校验顺序混乱问题,还是需要用到一开始讲到的@GroupSequence注解,但是,有两个问题:</p>
<ol><li>Dto中还有一个对象类型的属性。</li><li>对象类型属性中的子属性(id、channelId)也要成功校验</li></ol>
<p class="maodian"></p><h3>解决方案</h3>
<p>首先,为了让Dto中的对象类型的属性也能正常校验,需要添加@Valid注解;</p>
<p>然后,在对象类型属性中的子属性中也需要像文章前面所说的在校验注解中设置groups属性。</p>
<p>修改后的代码如下:</p>
<div class="jb51code"><pre class="brush:java;">@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&lt;CommonDto&gt; 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;
}
</pre></div>
<p>如果CommonDto中校验属性的注解不设置groups,CommonDto中的校验属性就会失效,这是个<strong>大坑</strong>。</p>
<p class="maodian"></p><h2>总结</h2>
<p>以上为个人经验,希望能给大家一个参考,也希望大家多多支持琼殿技术社区。</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>javax.validation在Spring Boot请求中使用方式</li><li>spring中的参数校验技术:jakarta.validation使用详解</li><li>SpringBoot&nbsp;@ConfigurationProperties&nbsp;+&nbsp;Validation实现启动期校验解决方案</li><li>Spring&nbsp;Validation数据校验详解</li><li>SpringBoot利用validation实现数据校验完整指南</li><li>基于Spring&nbsp;Validation实现全局参数校验异常处理的示例详解</li><li>如何在Spring&nbsp;Boot&nbsp;项目中自定义&nbsp;Validation&nbsp;注解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Spring Validation的校验顺序问题及解决过程