前言

在 Java 项目中,如果你不想自己手写一堆 if (xxx == null) 的参数校验逻辑,那么目前有且只有一个事实标准Spring + Hibernate Validator 的组合

而且,为了用得顺手,你必须把这两者结合起来用 —— 单独用 Hibernate Validator 或者只靠 Spring,都会遇到各种奇怪的问题。


本文不讲的内容

  • 基础校验注解(如 @NotNull@Min@Max 等)
  • 自定义校验注解怎么写

因为这些内容网上一搜一大把,而且基本没有坑,照着抄就行。


真正让人头大的地方

这个校验体系最大的问题在于:同一个注解,在 Controller 和其他 Bean 中的行为不一致!

不说底层原理了,直接上用法。


一、在 Controller 中怎么用?

✅ 普通校验注解 + 自定义校验注解,直接写在方法参数或返回值上就能生效

@GetMapping("/xxx/{id}")
public @NotNull String xxx(@PathVariable @Min(2) @Max(5) Integer id) {
    System.out.println(" xxx ");
    return transform(id);
}

✅ 如果你想让对象内部的字段校验生效(比如 @Length@Range),需要用 @Valid@Validated —— 这俩在这里作用一样,都是“激活器”

@Data
public class TestValidBean {
    @Length(min = 2, max = 5)
    private String name;
    @Range(min = 2, max = 5)
    private Integer age;
}

@PostMapping("/test")
public String test(@RequestBody @Validated @Valid TestValidBean testValidBean) {
    log.warn("name = {} , age = {}", testValidBean.getName(), testValidBean.getAge());
    return testValidBean.getName();
}

💡 小贴士:这里写 @Validated@Valid,或者两个都写,效果都一样。Spring MVC 会帮你处理。


二、在非 Controller 的 Bean 中(比如 Service、Repository、Interface)

⚠️ 类上必须加 @Validated —— 它的作用和 JPA 中的 @Entity 类似,是用来“激活”校验机制的。

✅ 普通校验和自定义校验注解,写法和 Controller 一样:

@Validated
public interface BookService {
    int deleteById(@NotNull @Min(1) Integer id);
}

✅ 如果要校验对象内部字段只能用 @Valid

@Validated
public interface BookService {
    int add(@Valid Book book);
}

❗ 注意:这里不能用 @Validated 替代 @Valid,否则对象内部的校验注解不会生效!


三、分组校验 —— 最混乱的部分

分组的思路很简单:

  • 校验注解可以加 groups = XXX.class 指定分组
  • 校验时传入分组,只校验对应分组的约束

但问题来了:Spring AOP 只认类上和方法上的 @Validated(groups=...),不认参数上的!

✅ 在 Controller 中:

你可以把 @Validated(Add.class) 写在:

  • 参数前 ✅
  • 方法前 ✅
  • 类前 ✅

都能生效!

@PostMapping("/test")
public String test(@RequestBody @Validated(Add.class) TestValidBean testValidBean) {
    log.warn("name = {} , age = {}", testValidBean.getName(), testValidBean.getAge());
    return testValidBean.getName();
}

💡 因为 Spring MVC 的参数绑定处理器会主动检查这些注解,所以很灵活。

⚠️ 在其他 Bean 中(比如 Service):

  • 不能写在参数前 —— 写了也没用!
  • ✅ 可以写在方法前 → 只对这个方法分组校验
  • ✅ 可以写在类前 → 对类中所有方法生效
@Validated
public interface BookService {
    @Validated(Book.Add.class)
    int add(@Valid Book book);
}

或者:

@Validated(Book.Add.class)
public interface BookService {
    int add(@Valid Book book);
}

吐槽时间 💢

我看这个校验框架的源码看得头都大了 —— 设计真是一塌糊涂!

最大的问题就是:行为不一致!

造成这种混乱的核心原因是:

Spring MVC 的代码里,到处都混着 validation 的逻辑:

  • 加载 MethodHandler 的时候有
  • 解析参数的时候有
  • 创建 MethodHandler 的时候也有

而且还专门为 @Validated 做了一堆特异化处理!

举个栗子:

  • 在非 Controller 中,@Validated 写在参数前 → 完全没用
  • 在 Controller 中,它不仅能分组,还能激活对象内部校验
  • 写在类上时,甚至不需要 AOP 代理就能生效!

现在 Spring Boot 把 validation 模块独立出来了,真希望他们能重构一下这个“祖传屎山”……


总结

场景普通/自定义校验对象内部校验分组校验写法
Controller直接写参数/返回值@Valid@Validated可写在参数/方法/类前
其他 Bean类上加 @Validated + 写参数只能用 @Valid❌ 写参数前无效,✅ 可写方法/类前

记住这张表,能帮你少踩 80% 的坑。


📌 最后提醒:虽然这套东西很烂,但目前没得选 —— 谁让它是“事实标准”呢?
能用就行,别想太多,早点下班 😅


注:基于SpringBoot3.5.3


后记:反复想了很多次校验注解的使用规则,我觉得spring validation的意图是:

  1. 在Controller中不推荐类上加@Validated,此时的@Validated的作用等于有分组能力的@Valid

  2. 在非Controller类中@Validated必须加在类上才会开启校验,分组能力是加在方法前的如:@Validated(Add.class),在非Controller中@Validated加在参数前没有作用。

  3. 虽然SpringMVC处理了在Controller类前加@Validated的逻辑,避免检验两次,但应不是推荐用法。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐