Spring MVC JSR-303 Bean Validation
JSR-303是Java标准的Bean Validation规范,定义了一套统一的校验注解和API。
JSR-303与Hibernate Validator关系
| 规范/实现 | 说明 |
|---|---|
| JSR-303 | Java Bean Validation 1.0规范 |
| JSR-349 | Bean Validation 1.1规范 |
| JSR-380 | Bean Validation 2.0规范 |
| Hibernate Validator | JSR规范的参考实现 |
JSR-303标准注解
| 注解 | 类型 | 说明 |
|---|---|---|
| @Null | 任意 | 必须为null |
| @NotNull | 任意 | 不能为null |
| @AssertTrue | boolean | 必须为true |
| @AssertFalse | boolean | 必须为false |
| @Min | 数字 | 最小值 |
| @Max | 数字 | 最大值 |
| @DecimalMin | 数字 | 最小值(字符串表示) |
| @DecimalMax | 数字 | 最大值(字符串表示) |
| @Size | 字符串/集合 | 大小范围 |
| @Digits | 数字 | 位数限制 |
| @Past | 日期 | 必须是过去时间 |
| @Future | 日期 | 必须是未来时间 |
| @Pattern | 字符串 | 正则匹配 |
JSR-380新增注解(Bean Validation 2.0)
| 注解 | 说明 |
|---|---|
| @NotEmpty | 不能为空(字符串、集合、数组) |
| @NotBlank | 不能为空白字符串 |
| 邦箱格式 | |
| @Positive | 必须为正数 |
| @PositiveOrZero | 正数或零 |
| @Negative | 必须为负数 |
| @NegativeOrZero | 负数或零 |
| @PastOrPresent | 过去或当前时间 |
| @FutureOrPresent | 未来或当前时间 |
基本校验示例
Java
@Data
public class UserDTO {
@NotNull(message = "ID不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度3-20字符")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄必须大于18")
@Max(value = 100, message = "年龄不能超过100")
private Integer age;
@Digits(integer = 10, fraction = 2, message = "金额格式不正确")
private BigDecimal amount;
@Past(message = "生日必须是过去的日期")
private Date birthday;
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "只能包含字母和数字")
private String code;
}
Controller校验流程
Java
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping
public User create(@Valid @RequestBody UserDTO dto) {
// 校验失败自动抛出MethodArgumentNotValidException
return userService.create(dto);
}
@PostMapping("/with-binding")
public User createWithBinding(
@Valid @RequestBody UserDTO dto,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<FieldError> errors = bindingResult.getFieldErrors();
for (FieldError error : errors) {
System.out.println(error.getField() + ": " + error.getDefaultMessage());
}
throw new ValidationException("参数校验失败");
}
return userService.create(dto);
}
}
级联校验
Java
@Data
public class OrderDTO {
@NotNull
private Long id;
@Valid // 触发级联校验
@NotNull
private AddressDTO address;
@Valid
@Size(min = 1, max = 10)
private List<ProductDTO> products;
}
@Data
public class AddressDTO {
@NotBlank
private String city;
@NotBlank
private String street;
}
校验分组
Java
// 定义校验分组
public interface CreateGroup {}
public interface UpdateGroup {}
@Data
public class UserDTO {
@NotNull(groups = UpdateGroup.class, message = "更新时ID必填")
private Long id;
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, message = "用户名必填")
private String username;
@NotBlank(groups = CreateGroup.class, message = "创建时密码必填")
private String password;
}
@RestController
public class UserController {
@PostMapping
public User create(@Validated(CreateGroup.class) @RequestBody UserDTO dto) {
// 只校验CreateGroup分组的注解
return userService.create(dto);
}
@PutMapping
public User update(@Validated(UpdateGroup.class) @RequestBody UserDTO dto) {
// 只校验UpdateGroup分组的注解
return userService.update(dto);
}
}
Validator接口使用
Java
@Service
public class ValidationService {
@Autowired
private Validator validator;
public void validate(Object object) {
Set<ConstraintViolation<Object>> violations = validator.validate(object);
if (!violations.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (ConstraintViolation<Object> violation : violations) {
sb.append(violation.getPropertyPath())
.append(": ")
.append(violation.getMessage())
.append("; ");
}
throw new ValidationException(sb.toString());
}
}
public void validate(Object object, Class<?>... groups) {
Set<ConstraintViolation<Object>> violations =
validator.validate(object, groups);
// 处理校验结果...
}
}
ConstraintViolation信息
Java
Set<ConstraintViolation<UserDTO>> violations = validator.validate(dto);
for (ConstraintViolation<UserDTO> violation : violations) {
// 获取校验信息
String message = violation.getMessage(); // 错误消息
String propertyPath = violation.getPropertyPath().toString(); // 属性路径
Object invalidValue = violation.getInvalidValue(); // 无效值
Class<?> annotationType = violation.getConstraintDescriptor()
.getAnnotation().annotationType(); // 校验注解类型
}
自定义校验注解
Java
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdCardValidator.class)
public @interface IdCard {
String message() default "身份证号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class IdCardValidator implements ConstraintValidator<IdCard, String> {
@Override
public void initialize(IdCard constraintAnnotation) {
// 初始化配置
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
// 身份证号校验逻辑
return value.matches("\\d{17}[\\dXx]");
}
}
Payload传递元信息
Java
public class ErrorLevel implements Payload {
private final String level;
public ErrorLevel(String level) { this.level = level; }
public String getLevel() { return level; }
}
@Data
public class UserDTO {
@NotNull(message = "ID必填", payload = ErrorLevel.ERROR.class)
private Long id;
@NotBlank(message = "用户名必填", payload = ErrorLevel.WARN.class)
private String username;
}
Spring MVC配置
Java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setProviderClass(HibernateValidator.class);
return factoryBean;
}
@Override
public Validator getValidator() {
return validator();
}
}
要点总结
- JSR-303是Java标准的Bean Validation规范
- Hibernate Validator是JSR规范的参考实现
- 标准注解包括@NotNull、@Size、@Min、@Max、@Pattern等
- Bean Validation 2.0新增@NotBlank、@Email、@Positive等
- @Valid触发级联校验,@Validated支持分组校验
- 可通过ConstraintValidator自定义校验注解
📝 发现内容有误?点击此处直接编辑