一、注解出处与核心定位
1. 注解来源
• 所属框架:@Validated
是 Spring Framework 提供的注解(org.springframework.validation.annotation
包下)。
• 核心定位:
作为 Spring 对 JSR-380(Bean Validation 2.0+) 规范的增强实现,用于触发方法参数、返回值或类成员变量的校验逻辑。
与标准Valid`(JSR 原生注解)相比,支持 分组验证 和 层级校验,更贴合 Spring 生态。
二、基础使用步骤
1. 添加依赖(Spring Boot 示例)
在 pom.xml
中引入 Bean Validation 实现:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. 启用校验支持
在 Spring Boot 中无需额外配置,自动生效。
3. 编写校验规则(DTO/VO)
在数据对象中标注 Bean Validation 注解java
public class UserCreateDTO {
@NotNull(message = "ID 不能为空")
private Long id;
@NotEmpty(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空或纯空格")
private String password;
@Size(min = 6, max = 20, message = "密码长度需在6-20位之间")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误")
private String phone;
@Min(value = 18, message = "年龄需≥18岁")
private Integer age;
@DecimalMin(value = "0.0", inclusive = false, message = "价格必须大于0")
private BigDecimal price;
@Positive(message = "库存必须为正数")
private Integer stock;
@NegativeOrZero(message = "折扣率需≤0")
private BigDecimal discountRate;
@Past(message = "出生日期必须早于当前时间")
private LocalDate birthday;
@Future(message = "预约时间必须晚于当前时间")
private LocalDateTime appointmentTime;
@Length(min = 2, max = 10, message = "名称长度需在2-10字符之间")
private String name;
@Range(min = 1, max = 100, message = "数量需在1-100之间")
private Integer quantity;
@URL(protocol = "https", message = "仅支持HTTPS链接")
private String website;
@CreditCardNumber(message = "信用卡号无效")
private String cardNumber;
@SafeHtml(message = "包含非法HTML标签")
private String description;
}
4. 触发校验(Controller 层)
在 Controller 方法参数前使用 @Validated
或 @Valid
:
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<?> createUser(
@RequestBody @Validated UserCreateDTO dto) { // 触发校验
// 业务逻辑
return ResponseEntity.ok().build();
}
}
5. 处理校验异常
通过 @ExceptionHandler
捕获校验失败异常,返回统一错误响应:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.toList();
return ResponseEntity.badRequest()
.body(new ErrorResponse("参数校验失败", errors));
}
}
三、进阶功能与使用拓展
1. 分组校验(多场景复用规则)
场景说明
- 创建用户时:所有字段必填
- 更新用户时:允许不修改密码(密码字段非必填)
步骤实现
1. 定义分组接口
public interface CreateGroup {} // 创建分组标记
public interface UpdateGroup {} // 更新分组标记
2. 在 DTO 中指定分组
public class UserDTO {
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
private String username;
@NotBlank(groups = CreateGroup.class) // 仅创建时校验
private String password;
}
3. Controller 方法指定生效分组
@PostMapping("/users")
public ResponseEntity<?> createUser(
@RequestBody @Validated(CreateGroup.class) UserDTO dto) {
// 仅校验 CreateGroup 分组规则
}
@PutMapping("/users/{id}")
public ResponseEntity<?> updateUser(
@PathVariable Long id,
@RequestBody @Validated(UpdateGroup.class) UserDTO dto) {
// 仅校验 UpdateGroup 分组规则
}
2. 方法级别校验(Service 层验证)
在 Service 接口/实现类中使用 @Validated
触发方法参数校验:
@Service
@Validated //级别开启校验
public class UserService {
public void updateEmail(
@NotNull Long userId,
@Email String newEmail) { // 直接标注校验规则
// 业务逻辑
}
}
3. 自定义校验注解
需求示例
验证手机号格式(自定义规则)。
实现步骤
1. 定义注解 @Phone
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Phone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2. 编写校验器 PhoneValidator
public class PhoneValidator implements ConstraintValidator<Phone, String> {
private static final Pattern PHONE_PATTERN =
Pattern.compile("^1[3-9]\\d{9}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) return true; // 结合 @NotNull 使用
return PHONE_PATTERN.matcher(value).matches();
}
}
3. 在 DTO 中使用
public class UserDTO {
@Phone
private String phone;
}
4. 级联校验(嵌套对象验证)
当对象包含其他对象成员时,使用 @Valid
触发嵌套校验:
public class OrderCreateDTO {
@Valid // 触发 AddressDTO 内部校验
@NotNull(message = "收货地址不能为空")
private AddressDTO address;
// 其他字段...
}
public class AddressDTO {
@NotBlank
private String city;
@NotBlank
private String street;
}
四、原理与关键机制
1. 校验触发流程
- 代理拦截:Spring 通过 AOP 代理拦截带有
@Validated
注解的类方法。 - 参数解析:在方法执行前,校验参数是否符合约束。
- 异常抛出:若校验失败,抛出
ConstraintViolationException
(方法级别)或MethodArgumentNotValidException
(Controller 层)。 - 异常处理:通过
HandlerExceptionResolver
或@ExceptionHandler
处理错误。
2. 与 @Valid
的核心差异
特性 | @Valid (JSR-380) |
@Validated (Spring) |
---|---|---|
分组支持 | 不支持 | 支持 |
校验范围 | 仅方法参数、字段 | 方法参数、返回值、类成员变量 |
层级校验触发 | 需显式添加 @Valid |
可自动级联 |
适用场景 | 简单 Bean 校验 | Spring 生态复杂校验需求 |
五、常见问题与排查
1. 校验注解不生效
• 可能原因:
• 未添加 spring-boot-starter-validation
依赖
• 未在类上标注 @Validated
(方法级别校验)
• 校验方法未被 Spring 代理(如内部方法调用)
2. 分组校验未按预期执行
• 检查点:
• Controller/Service 方法是否指定了正确的 groups
• 分组接口是否正确定义(空接口即可,无需实现)
3. 自定义校验器未生效
• 排查步骤:
• 确保 ConstraintValidator
实现类被 Spring 管理(如添加 @Component
)
• 检查注解的 @Constraint(validatedBy)
指向正确类
六、最佳实践总结
分层校验:
• Controller 层:处理基础数据格式校验
• Service 层:执行业务规则校验(如库存检查)合理分组:避免为不同场景创建重复 DTO,通过分组复用校验规则。
统一异常处理:全局捕获校验异常,返回结构化的错误信息。
性能优化:
• 避免在复杂校验中执行数据库操作(优先通过缓存或异步校验)
• 对高频接口禁用实时校验(如通过groups
动态控制)
通过灵活运用 @Validated
,开发者可以构建健壮维护的校验体系,显著提升代码质量和系统可靠性。