Spring Boot Validation 注解完全指南
概述
Spring Boot Validation 是基于 Jakarta Bean Validation 规范(JSR 380)的实现,提供了强大的数据校验功能。通过简单的注解,我们可以对方法参数、返回值、Bean属性等进行验证,确保数据的完整性和正确性。
依赖配置
在 pom.xml
中添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
核心注解详解
1. 空值校验注解
@NotNull
作用:验证字段值不为 null
适用类型:任何类型
示例:
@NotNull(message = "用户ID不能为空")
private String userId;
@NotBlank
作用:验证字符串不为 null 且长度大于0(去除前后空白)
适用类型:String
示例:
@NotBlank(message = "用户名不能为空")
private String userName;
@NotEmpty
作用:验证字段不为 null 且不为空(集合/数组长度>0,字符串长度>0)
适用类型:String、Collection、Map、Array
示例:
@NotEmpty(message = "角色列表不能为空")
private List<String> roles;
2. 长度校验注解
@Size
作用:验证字符串、集合或数组的长度在指定范围内
参数:
min
:最小长度max
:最大长度message
:错误消息
示例:
@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
private String userName;
@Size(max = 200, message = "描述长度不能超过200个字符")
private String description;
3. 数值范围校验注解
@Min
作用:验证数字字段的最小值
示例:
@Min(value = 1, message = "页码不能小于1")
private Integer page;
@Max
作用:验证数字字段的最大值
示例:
@Max(value = 100, message = "每页大小不能大于100")
private Integer size;
@DecimalMin
作用:验证数字字段的最小值(支持字符串格式)
示例:
@DecimalMin(value = "0.0", inclusive = false, message = "价格必须大于0")
private BigDecimal price;
@DecimalMax
作用:验证数字字段的最大值(支持字符串格式)
示例:
@DecimalMax(value = "10000.0", message = "价格不能超过10000")
private BigDecimal price;
4. 格式校验注解
作用:验证字段值是否符合邮箱格式
示例:
@Email(message = "邮箱格式不正确")
private String email;
@Pattern
作用:使用正则表达式验证字段值格式
示例:
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
常用正则表达式模式:
- 手机号:
^1[3-9]\\d{9}$
- 身份证:
^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$
- 邮编:
^[1-9]\\d{5}$
5. 布尔校验注解
@AssertTrue
作用:验证布尔字段值为 true
示例:
@AssertTrue(message = "必须同意协议")
private Boolean agreed;
@AssertFalse
作用:验证布尔字段值为 false
示例:
@AssertFalse(message = "不能是测试用户")
private Boolean isTest;
6. 日期时间校验注解
@Past
作用:验证日期时间字段是过去时间
示例:
@Past(message = "生日必须是过去时间")
private LocalDate birthday;
@PastOrPresent
作用:验证日期时间字段是过去或现在时间
示例:
@PastOrPresent(message = "创建时间不能是未来时间")
private LocalDateTime createTime;
@Future
作用:验证日期时间字段是未来时间
示例:
@Future(message = "过期时间必须是未来时间")
private LocalDateTime expireTime;
@FutureOrPresent
作用:验证日期时间字段是未来或现在时间
示例:
@FutureOrPresent(message = "计划时间不能是过去时间")
private LocalDateTime planTime;
7. 复杂对象校验注解
@Valid
作用:级联验证,验证嵌套对象
示例:
@Valid
@NotNull(message = "用户信息不能为空")
private UserInfo userInfo;
分组校验
创建校验分组
public interface CreateGroup {}
public interface UpdateGroup {}
使用分组校验
@NotBlank(groups = CreateGroup.class, message = "用户名不能为空")
private String userName;
@NotNull(groups = UpdateGroup.class, message = "用户ID不能为空")
private Long id;
控制器中使用分组
@PostMapping("/create")
public R<User> createUser(@Validated(CreateGroup.class) @RequestBody User user) {
// 业务逻辑
}
@PutMapping("/update")
public R<User> updateUser(@Validated(UpdateGroup.class) @RequestBody User user) {
// 业务逻辑
}
自定义校验注解
1. 创建自定义注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2. 实现校验逻辑
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. 使用自定义注解
@Phone(message = "手机号格式不正确")
private String phone;
控制器中使用验证
基本用法
@PostMapping("/login")
public R<LoginToken> login(@Valid @RequestBody LoginRequest request) {
return authService.login(request);
}
获取校验错误信息
@PostMapping("/register")
public R<User> register(@Valid @RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
// 处理校验错误
List<FieldError> errors = result.getFieldErrors();
String errorMessage = errors.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
return R.fail(errorMessage);
}
// 业务逻辑
}
全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<Object> handleValidationException(MethodArgumentNotValidException ex) {
List<FieldError> errors = ex.getBindingResult().getFieldErrors();
Map<String, String> errorMap = new HashMap<>();
for (FieldError error : errors) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
return R.fail("参数校验失败").data(errorMap);
}
}
实战示例
用户注册DTO
@Data
public class RegisterRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度3-20字符")
private String username;
@NotBlank(message = "密码不能为空")
@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岁")
@Max(value = 100, message = "年龄必须小于100岁")
private Integer age;
@AssertTrue(message = "必须同意用户协议")
private Boolean agreed;
}
商品信息DTO
@Data
public class ProductDto {
@NotBlank(message = "商品名称不能为空")
@Size(max = 100, message = "商品名称不能超过100字符")
private String name;
@DecimalMin(value = "0.0", inclusive = false, message = "价格必须大于0")
@DecimalMax(value = "999999.99", message = "价格不能超过999999.99")
private BigDecimal price;
@Min(value = 0, message = "库存不能为负数")
private Integer stock;
@Future(message = "过期时间必须是未来时间")
private LocalDateTime expireDate;
@Valid
@NotNull(message = "分类信息不能为空")
private Category category;
}
最佳实践
1. 合理的错误消息
- 提供具体、明确的错误消息
- 避免技术术语,使用用户友好的语言
- 包含具体的约束信息
2. 校验顺序
@NotBlank(message = "不能为空")
@Size(min = 6, max = 20, message = "长度6-20字符")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$", message = "必须包含大小写字母和数字")
private String password;
3. 性能考虑
- 避免复杂的正则表达式
- 对于频繁调用的接口,考虑缓存校验结果
- 使用分组校验减少不必要的验证
4. 安全性
- 不要依赖前端验证,后端必须进行验证
- 对敏感数据使用额外的安全验证
- 验证输入数据的范围和格式,防止注入攻击
常见问题排查
1. 验证不生效
- 检查是否添加了
@Valid
或@Validated
注解 - 确认依赖已正确添加
- 检查控制器方法参数是否正确
2. 错误消息不显示
- 检查是否有全局异常处理
- 确认消息模板配置正确
3. 自定义注解不工作
- 检查
ConstraintValidator
实现是否正确 - 确认注解元数据配置正确
总结
Spring Boot Validation 提供了强大而灵活的数据验证机制,通过合理的注解组合可以满足大多数业务场景的验证需求。掌握这些注解的用法,可以显著提高代码质量和系统安全性。
注解类型 | 主要注解 | 适用场景 |
---|---|---|
空值校验 | @NotNull, @NotBlank, @NotEmpty | 必填字段验证 |
长度校验 | @Size | 字符串、集合长度限制 |
数值校验 | @Min, @Max, @DecimalMin, @DecimalMax | 数值范围限制 |
格式校验 | @Email, @Pattern | 数据格式验证 |
布尔校验 | @AssertTrue, @AssertFalse | 布尔值验证 |
日期校验 | @Past, @Future 等 | 时间验证 |
级联校验 | @Valid | 嵌套对象验证 |
通过合理运用这些注解,可以构建出健壮、安全的Spring Boot应用程序。