Java 参数校验总结:Spring + Hibernate Validator 的那些坑(@Valid和@Validated傻傻分不清?)
前言
在 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的意图是:
-
在Controller中不推荐类上加@Validated,此时的@Validated的作用等于有分组能力的@Valid
-
在非Controller类中@Validated必须加在类上才会开启校验,分组能力是加在方法前的如:@Validated(Add.class),在非Controller中@Validated加在参数前没有作用。
-
虽然SpringMVC处理了在Controller类前加@Validated的逻辑,避免检验两次,但应不是推荐用法。
更多推荐



所有评论(0)