1. JSR303 是啥?为什么会有它
JSR 是 Java Specification Request(Java 规范请求),相当于 Java 官方给出的"行业标准"。
而 JSR303 全称是 Bean Validation 1.0,它是一套标准化的 Java Bean 校验规范。
简单来说:
-
以前我们做参数校验可能是这样的:
scssif (user.getUsername() == null || user.getUsername().trim().isEmpty()) { throw new IllegalArgumentException("用户名不能为空"); } if (!user.getEmail().matches(...)) { throw new IllegalArgumentException("邮箱格式不正确"); }
这种写法问题是:代码到处都是 if,规则分散,不好维护。
-
有了 JSR303,我们直接在模型(DTO/Entity)上声明校验规则,框架帮我们自动检查,不符合就抛出异常:
kotlinpublic 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;
这样切换语言文件即可实现多语言提示。