SpringValidation参数校验
Spring Validation 基于 JSR-380(Bean Validation 2.0),提供了一套完整的参数校验机制。
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
|
常用校验注解
空值检查
| 注解 |
说明 |
| @Null |
必须为 null |
| @NotNull |
不能为 null |
| @NotBlank |
不能为 null 且至少一个非空白字符 |
| @NotEmpty |
不能为 null 且不为空(字符串/集合/数组) |
数值检查
| 注解 |
说明 |
| @Min |
最小值 |
| @Max |
最大值 |
| @Range |
范围(@Min + @Max) |
| @DecimalMin |
最小小数 |
| @DecimalMax |
最大小数 |
| @Positive |
正数 |
| @PositiveOrZero |
正数或零 |
| @Negative |
负数 |
| @NegativeOrZero |
负数或零 |
| @Digits |
小数位数限制 |
字符串检查
| 注解 |
说明 |
| @Size |
长度/大小范围 |
| @Length |
长度范围(Hibernate) |
| @Pattern |
正则匹配 |
| @Email |
邮箱格式 |
| @URL |
URL 格式(Hibernate) |
| @CreditCardNumber |
信用卡号(Hibernate) |
时间检查
| 注解 |
说明 |
| @Future |
未来时间 |
| @FutureOrPresent |
未来或现在 |
| @Past |
过去时间 |
| @PastOrPresent |
过去或现在 |
基本使用
实体校验
@Data public class UserCreateRequest { @NotBlank(message = "用户名不能为空") @Size(min = 2, max = 20, message = "用户名长度2-20") private String username; @NotBlank(message = "密码不能为空") @Size(min = 6, message = "密码至少6位") @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d).+$", message = "密码必须包含字母和数字") private String password; @NotBlank(message = "邮箱不能为空") @Email(message = "邮箱格式不正确") private String email; @Min(value = 1, message = "年龄不能小于1") @Max(value = 150, message = "年龄不能大于150") private Integer age; @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") private String phone; }
|
控制器使用
@RestController @RequestMapping("/users") public class UserController { @PostMapping public Result<Long> create(@Valid @RequestBody UserCreateRequest request) { return Result.success(userService.create(request)); } @PutMapping("/{id}") public Result<Void> update(@PathVariable Long id, @Valid @RequestBody UserUpdateRequest request) { userService.update(id, request); return Result.success(); } }
|
分组校验
public interface CreateGroup {} public interface UpdateGroup {} public interface DeleteGroup {}
@Data public class UserRequest { @NotNull(groups = UpdateGroup.class, message = "ID不能为空") private Long id; @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}) @Size(min = 2, max = 20, groups = {CreateGroup.class, UpdateGroup.class}) private String username; @NotBlank(groups = CreateGroup.class) @Size(min = 6, groups = CreateGroup.class) private String password; @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}) @Email(groups = {CreateGroup.class, UpdateGroup.class}) private String email; }
|
@RestController public class UserController { @PostMapping("/users") public Result<Long> create( @Validated(CreateGroup.class) @RequestBody UserRequest request) { return Result.success(userService.create(request)); } @PutMapping("/users/{id}") public Result<Void> update( @Validated(UpdateGroup.class) @RequestBody UserRequest request) { userService.update(request); return Result.success(); } }
|
嵌套校验
@Data public class OrderCreateRequest { @NotBlank(message = "订单编号不能为空") private String orderNo; @NotNull(message = "用户ID不能为空") private Long userId; @Valid @NotEmpty(message = "订单项不能为空") private List<OrderItemRequest> items; @Valid @NotNull(message = "地址不能为空") private AddressRequest address; }
@Data public class OrderItemRequest { @NotBlank(message = "SKU不能为空") private String skuId; @Min(value = 1, message = "数量至少为1") private Integer quantity; @DecimalMin(value = "0.01", message = "单价必须大于0") private BigDecimal price; }
@Data public class AddressRequest { @NotBlank(message = "收件人不能为空") private String receiver; @NotBlank(message = "手机号不能为空") @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") private String phone; @NotBlank(message = "地址不能为空") private String detail; }
|
自定义校验注解
手机号校验
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PhoneValidator.class) public @interface Phone { String message() default "手机号格式不正确"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
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 || value.isEmpty()) { return true; } return PHONE_PATTERN.matcher(value).matches(); } }
|
枚举值校验
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = EnumValueValidator.class) public @interface EnumValue { String message() default "枚举值不正确"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; Class<? extends Enum<?>> enumClass(); }
public class EnumValueValidator implements ConstraintValidator<EnumValue, String> { private Set<String> values; @Override public void initialize(EnumValue annotation) { values = Arrays.stream(annotation.enumClass().getEnumConstants()) .map(Enum::name) .collect(Collectors.toSet()); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) return true; return values.contains(value); } }
|
使用自定义注解
@Data public class UserRequest { @Phone private String phone; @EnumValue(enumClass = UserStatus.class, message = "用户状态不正确") private String status; }
public enum UserStatus { ACTIVE, INACTIVE, DELETED }
|
手动触发校验
@Service public class UserService { @Autowired private Validator validator; public void createUser(UserCreateRequest request) { Set<ConstraintViolation<UserCreateRequest>> violations = validator.validate(request); if (!violations.isEmpty()) { String message = violations.stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining(", ")); throw new ValidationException(message); } } public void validateUsername(String username) { Set<ConstraintViolation<UserCreateRequest>> violations = validator.validateValue(UserCreateRequest.class, "username", username); } public void updateUser(UserRequest request) { Set<ConstraintViolation<UserRequest>> violations = validator.validate(request, UpdateGroup.class); } }
|
全局异常处理
@RestControllerAdvice public class ValidationExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public Result<Void> handleMethodArgumentNotValid(MethodArgumentNotValidException e) { String message = e.getBindingResult().getFieldErrors().stream() .map(error -> error.getField() + error.getDefaultMessage()) .collect(Collectors.joining(", ")); return Result.fail(400, message); } @ExceptionHandler(ConstraintViolationException.class) public Result<Void> handleConstraintViolation(ConstraintViolationException e) { String message = e.getConstraintViolations().stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining(", ")); return Result.fail(400, message); } }
|
总结
Spring Validation 提供了优雅的参数校验方案:
- 注解驱动:声明式校验,无侵入
- 分组校验:不同场景使用不同规则
- 嵌套校验:支持复杂对象结构
- 自定义扩展:满足业务特定规则
- 手动触发:灵活控制校验时机
合理使用参数校验,可以在入口层就拦截非法数据,减少后续业务逻辑的防御性代码。