Spring 中的 @Validated 和 @Valid 注解有什么区别?
- 标签:后端、Spring
🏛 业务架构师
两者都触发 Bean Validation 校验,区别在来源和粒度:
- @Valid:JSR/Jakarta 标准注解,来自
jakarta.validation包。支持嵌套校验(对象里套对象,字段上标 @Valid 级联下钻),但只能标在参数、字段、方法上; - @Validated:Spring 提供,在标准能力上增加分组校验(同一 DTO 在"创建"和"更新"场景校不同规则)和方法级校验(直接标在 Service 类/方法上,参数约束生效)。
java
// Controller 入口用 @Validated/@Valid 开启 DTO 校验
public Result create(@Validated(Create.class) @RequestBody OrderDTO dto) {}
// 嵌套对象在字段上补 @Valid 级联业务架构师视角,参数校验是业务规则的第一道防线,我的治理要求:
- 入参校验在接入层完成:DTO 上
@NotNull/@Size/@Pattern+ 分组,脏数据进门即拒(400),不消耗下游服务与数据库资源——大促时这直接是容量问题; - 校验规则与业务语义对齐:手机号格式、金额区间这些写在 DTO 注解上,比散在 if 里更可审计;但跨字段的业务规则(库存>0 且 用户等级达标)属于领域逻辑,放 Service,不要硬塞进校验注解——校验框架管"形状",领域层管"规则";
- 校验失败统一异常翻译成标准错误响应,前端能按字段定位提示。
🏗 系统架构师
- @Valid(JSR 标准注解):标在参数/字段上做级联校验——对象内嵌套对象递归生效;
- @Validated(Spring 扩展):标在类上开启方法级参数校验(AOP 拦截),并支持分组校验——同一对象按场景应用不同规则(Create 组与 Update 组字段要求不同)。
典型组合:Controller 入参 @Valid 触发嵌套校验;Service 方法 @Validated 加参数约束兜底内部调用。
架构视角——校验放哪一层:
- 边界 fail-fast:外部输入在 API 入口校验完,脏数据不进系统;MethodArgumentNotValidException 统一转 400 契约;
- 不在每层重复深校验:入口校验格式与约束,领域层校验业务不变量(金额大于零且不超余额),两者语义不同;
- 方法级校验本质是 AOP,注意自调用失效;
- 校验规则沉淀为平台约束注解(手机号、身份证),一致性靠复用而不是每个开发者各写一份。
💻 全栈开发专家
核心差异对比:
| 维度 | @Valid | @Validated |
|---|---|---|
| 来源 | Jakarta Bean Validation 标准 | Spring 提供 |
| 分组校验 | 不支持 | 支持(group) |
| 嵌套校验 | 支持(字段上再标触发递归) | 不支持 |
| 常用位置 | Controller 入参 | 类/方法/参数(Service 层校验) |
典型组合:
java
@PostMapping
public Result create(@RequestBody @Valid OrderReq req) { ... }
// 失败抛 MethodArgumentNotValidException → 400
@Service
@Validated
public class OrderService {
public void cancel(@NotNull @Min(1) Long orderId) { ... }
}
// 失败抛 ConstraintViolationException两个必踩坑:
- 两种异常类型不同,全局异常处理器必须分开接,漏一个前端就收到 500 而不是 400
- 嵌套对象必须在内层字段加
@Valid(如@Valid private List<ItemReq> items),漏标后内层规则静默不生效——"校验配了但不生效"九成是这个
@Validated 的分组能力:定义 Create/Update 组,同一 POJO 不同接口校验不同字段集(更新时 id 必填、创建时必空),这是它存在的核心价值。