使用spring-boot-starter-validation常用注释优雅判断类型

发布于:2025-09-05 ⋅ 阅读:(20) ⋅ 点赞:(0)

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

作用:验证字段值是否符合邮箱格式
示例

@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应用程序。


网站公告

今日签到

点亮在社区的每一天
去签到