《JSR303 数据校验全攻略:从入门到实战,玩转优雅的参数验证》

1. JSR303 是啥?为什么会有它

JSR 是 Java Specification Request(Java 规范请求),相当于 Java 官方给出的"行业标准"。

JSR303 全称是 Bean Validation 1.0,它是一套标准化的 Java Bean 校验规范。

简单来说:

  • 以前我们做参数校验可能是这样的:

    scss 复制代码
    if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
        throw new IllegalArgumentException("用户名不能为空");
    }
    if (!user.getEmail().matches(...)) {
        throw new IllegalArgumentException("邮箱格式不正确");
    }

    这种写法问题是:代码到处都是 if,规则分散,不好维护。

  • 有了 JSR303,我们直接在模型(DTO/Entity)上声明校验规则,框架帮我们自动检查,不符合就抛出异常:

    kotlin 复制代码
    public class UserDTO {
        @NotBlank(message = "用户名不能为空")
        private String username;
    
        @Email(message = "邮箱格式不正确")
        private String email;
    }

    这样不但干净,还能一次声明,多处复用,也方便国际化、统一异常处理。


2. 谁来帮它工作?

JSR303 只是标准 ,具体执行需要一个实现 。最常见的实现是 Hibernate Validator(Spring Boot 默认集成的就是它)。

你可以把它想象成:

  • JSR303 = "交通法规"
  • Hibernate Validator = "交警队"

3. 常用的校验注解(带小贴士)

空值相关

注解 说明 常见场景
@NotNull 不能为 null(长度可以为 0) 数据库主键、对象
@NotEmpty 不能为 null 且长度 > 0 集合、数组、字符串
@NotBlank 不能为 null 且必须包含至少一个非空格字符 用户输入的文本

小贴士

  • String,多数场景用 @NotBlank
  • 对集合/数组用 @NotEmpty
  • @NotNull 只保证非空,不管长度。

长度/大小

注解 说明
@Size(min, max) 字符串、集合、数组长度范围
@Min / @Max 数值上下限(整数)
@DecimalMin / @DecimalMax 数值上下限(支持小数)
@Positive / @Negative 必须为正数/负数
@PositiveOrZero / @NegativeOrZero 必须为正数/负数或 0

格式相关

注解 说明
@Pattern(regexp) 正则表达式匹配
@Email 邮箱格式
@URL URL 格式(Hibernate 扩展)

日期时间

注解 说明
@Past 必须是过去的时间
@Future 必须是未来的时间
@PastOrPresent 过去或现在
@FutureOrPresent 未来或现在

嵌套对象

注解 说明
@Valid 用于校验嵌套对象(如果 UserDTO 里有 ProfileDTO,且 ProfileDTO 也有注解,就得加它)

4. 在 Spring 里怎么用?

4.1 基础用法

less 复制代码
@RestController
public class UserController {

    @PostMapping("/users")
    public String createUser(@Valid @RequestBody UserDTO user) {
        return "ok";
    }
}

如果 user 不符合规则,Spring 会自动抛出 MethodArgumentNotValidException


4.2 分组校验

创建和更新规则可能不同:

kotlin 复制代码
public interface Create {}
public interface Update {}

public class UserDTO {
    @Null(groups = Create.class, message = "创建时 ID 必须为空")
    @NotNull(groups = Update.class, message = "更新时 ID 不能为空")
    private Long id;
}

Controller:

less 复制代码
@PostMapping
public String create(@Validated(Create.class) @RequestBody UserDTO dto) { ... }

@PutMapping
public String update(@Validated(Update.class) @RequestBody UserDTO dto) { ... }

这里必须用 @Validated@Valid 不支持分组)。


5. 常见异常类型

异常 触发场景
MethodArgumentNotValidException @RequestBody 校验失败
BindException 表单提交绑定失败
ConstraintViolationException 方法参数校验失败(非 @RequestBody
ValidationException 校验框架的基础异常

6. 全局异常处理

@ControllerAdvice + @ExceptionHandler 统一处理:

typescript 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleValidation(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .toList();
        return ResponseEntity.badRequest().body(Map.of("errors", errors));
    }
}

7. 自定义注解

比如公司内部手机号规则:

less 复制代码
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ FIELD })
@Retention(RUNTIME)
public @interface Phone {
    String message() default "手机号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class PhoneValidator implements ConstraintValidator<Phone, String> {
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && value.matches("^1\d{10}$");
    }
}

8. 程序化校验(不用注解也能校验)

scss 复制代码
@Autowired
private Validator validator;

public void check(UserDTO user) {
    Set<ConstraintViolation<UserDTO>> violations = validator.validate(user);
    if (!violations.isEmpty()) {
        violations.forEach(v -> System.out.println(v.getPropertyPath() + " " + v.getMessage()));
    }
}

9. 国际化(i18n)

ValidationMessages.properties 里:

ini 复制代码
user.username.notblank=用户名不能为空

DTO:

ini 复制代码
@NotBlank(message = "{user.username.notblank}")
private String username;

这样切换语言文件即可实现多语言提示。

相关推荐
悟纤2 小时前
当生产环境卡成 PPT:Spring Boot 线程 Dump 捉妖指南 - 第544篇
java·spring boot·后端
江影影影3 小时前
Spring Boot 2.6.0+ 循环依赖问题及解决方案
java·spring boot·后端
快乐就是哈哈哈4 小时前
Linux 部署与管理 Spring Boot 项目保姆级全流程
后端
codeGoogle4 小时前
昇腾揭开的伤疤,刺痛了谁?
后端
null不是我干的4 小时前
黑马SpringBoot+Elasticsearch作业2实战:商品搜索与竞价排名功能实现
spring boot·后端·elasticsearch
bobz9654 小时前
python3 包和项目管理工具 uv
后端
Re2757 小时前
揭秘索引的 “快”:从翻书到 B+ 树的效率革命
后端
David爱编程7 小时前
Java 三目运算符完全指南:写法、坑点与最佳实践
java·后端
学习编程的小羊8 小时前
Spring Boot 全局异常处理与日志监控实战
java·spring boot·后端