Spring Boot 参数校验进阶:抛弃复杂的 Group 分组,用 @AssertTrue 实现“动态逻辑校验”

一、 背景:标准注解不够用了怎么办?

在 Spring Boot 开发中,我们习惯了使用 @NotNull, @Size, @Pattern 来校验参数。但是,业务往往比这复杂得多。

场景举例:

我们有一个用户保存接口(UserSaveReqVO),既用于新增 ,也用于修改。

  1. 新增时 :id 为空,但 password 必须填。
  2. 修改时 :id 必填,但 password 可以为空(为空代表不修改密码)。

痛点 :

如果直接在 password 字段上加 @NotBlank,那么修改接口也会报错。如果不加,新增接口就不安全。怎么解决?


二、 什么是 @AssertTrue?

@AssertTrue 是 Bean Validation (Hibernate Validator) 提供的一个标准注解。

  • 字面含义:断言为真。
  • 作用对象 :通常用于 Boolean 类型的字段 或 返回 Boolean 类型的方法。
  • 校验规则 :
    • 如果值为 true,校验通过。
    • 如果值为 false,校验失败 ,抛出异常并返回 message 中的错误信息。

核心价值 :它允许我们在 DTO 内部编写一段 Java 代码,来进行自定义的、跨字段的、带有业务逻辑的校验。


三、 实战示例:一行代码搞定"新增必填,修改选填"

我们不需要定义两个 DTO,也不需要搞复杂的 Group,直接在 VO 内部写一个方法即可。

java 复制代码
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.AssertTrue;
import java.util.Objects;

@Data
@Schema(description = "用户保存请求 VO")
public class UserSaveReqVO {

    @Schema(description = "用户ID (新增为空,修改必填)")
    private Long id;

    @Schema(description = "账号")
    private String username;

    @Schema(description = "密码")
    private String password;

    // =================================================
    //  核心黑科技:自定义逻辑校验
    // =================================================

    @AssertTrue(message = "新增用户时,密码不能为空")
    @JsonIgnore // ⚠️ 坑点预警:必须加这个,否则会多出一个名为 passwordValid 的字段返回给前端
    public boolean isPasswordValid() {
        // 逻辑拆解:
        
        // 1. 如果 id 不为空,说明是"修改模式"。
        //    修改模式下对密码没要求,直接返回 true (通过)。
        if (id != null) {
            return true;
        }

        // 2. 如果 id 为空,说明是"新增模式"。
        //    新增模式下,密码必须有值。
        return password != null && !password.trim().isEmpty();
    }
}

Controller 使用方式:

完全不用改,照常加 @Valid 即可。

java 复制代码
@PostMapping("/save")
public CommonResult<Boolean> saveUser(@Valid @RequestBody UserSaveReqVO reqVO) {
    // 如果校验失败,这里进不来,全局异常处理器会处理
    return success(userService.saveUser(reqVO));
}

四、 为什么它比 Validation Groups (分组校验) 更好?

很多教程会推荐使用 groups 属性来解决这个问题,但在实战工程中,@AssertTrue 往往更胜一筹。

1. 避免"注解地狱"与"类爆炸"
  • Groups 方案 :你需要定义 CreateGroup 和 UpdateGroup 接口。然后字段上写 @Null(groups=Create.class), @NotNull(groups=Update.class)... 代码看起来很乱。
  • AssertTrue 方案 :逻辑内聚在一个 Java 方法里,if-else 清晰易读。
2. 代码高内聚 (High Cohesion)
  • Groups 方案 :校验逻辑是割裂 的。DTO 定义规则,Controller 决定触发哪个 Group。如果你在 Controller 里忘了写 @Validated(CreateGroup.class),校验就失效了,容易出 Bug。
  • AssertTrue 方案 :校验逻辑完全封装在 DTO 内部 。Controller 不需要关心是新增还是修改,只要加个 @Valid,DTO 自己会根据 ID 是否为空来判断该检查什么。
3. 解决"跨字段依赖"问题

这是 Group 做不到的。

  • 需求 :如果 type 是 "手机注册",则 mobile 必填;如果 type 是 "邮箱注册",则 email 必填。
  • AssertTrue :直接写个 if (type==1) return mobile != null;,轻松搞定。

五、 避坑指南

在使用 @AssertTrue 这种方法级校验时,有两个细节必须注意:

  1. 方法名必须以 is 或 get 开头 :
    校验器是按照 JavaBean 规范来找方法的。建议使用 isValid() 或 isXxxValid()。
  2. 必须加上 @JsonIgnore :
    这是最容易踩的坑。如果不加,Jackson 序列化时会把这个方法当成一个属性(比如 passwordValid: true)返回给前端,或者导致 Swagger 文档里多出莫名其妙的字段。

六、 总结

  • 简单校验 (非空、长度、正则):直接用 @NotNull, @Size 等标准注解。
  • 场景区分 (新增/修改)或 跨字段逻辑 (A有值则B必填):优先推荐使用 @AssertTrue + 自定义方法。

它让你的代码更内聚 、更易读,并且避免了 Controller 层繁琐的分组配置。

相关推荐
吠品41 分钟前
CSS图片模糊过渡:一个transition加filter就够
java·数据库·notepad++
步行cgn1 小时前
Spring 全注解开发详解
java·后端·spring
长河2 小时前
Java的GC(垃圾回收)中,标记无用对象的核心方法是可达性分析算法
java·jvm·算法
code2cat2 小时前
Java进阶篇之LongAdder:把统计更新分散,再读取总和
java·并发编程·原子类·longadder
凯哥Java2 小时前
写代码怎么避免逻辑漏洞?
java·开发语言·人工智能·自动化
Ivanqhz2 小时前
正余弦位置编码(Sinusoidal Positional Encoding)
java·linux·数据库
BD_Marathon3 小时前
消息对象中字段的说明
java·前端·python
zlpzlpzyd3 小时前
nacos的openapi地址
java
落魄实习生4 小时前
Agent Scope Java 2.x 系列【10】Middleware
java·开发语言·ai
IT_陈寒4 小时前
SpringBoot启动慢得像蜗牛?原来是这个配置在捣鬼
前端·人工智能·后端