2025年07月12日/ 浏览 5
在Web开发中,前端校验永远不能替代后端验证。我曾在一个电商项目中遇到惨痛教训:由于没有完善的后端校验,攻击者通过Postman直接提交负数价格,导致优惠券系统被薅羊毛。这正是Hibernate Validator的用武之地——它作为Bean Validation标准(JSR-380)的参考实现,能帮我们:
xml
<!-- pom.xml必备依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Spring Boot 2.3+版本已自动包含Hibernate Validator 6.x,无需单独指定版本。有趣的是,如果你查看依赖树,会发现spring-boot-starter-web其实已经间接引入了validation starter。
在用户注册场景中,我们需要验证:
java
public class UserDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 4, max = 20, message = "用户名长度4-20个字符")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$",
message = "密码需包含大小写字母和数字")
private String password;
@Future(message = "生日不能早于当前日期")
private LocalDate birthday;
}
这些注解构成了一道严密的防御网。特别提醒:@NotNull
和@NotBlank
的区别在于前者允许空字符串,后者连空格都不允许。
当内置注解不能满足需求时,可以创建如手机号验证的定制注解:
java
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface ValidPhone {
String message() default “手机号格式错误”;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PhoneValidator implements
ConstraintValidator<ValidPhone, String> {
private static final Pattern PATTERN =
Pattern.compile("^1[3-9]\\d{9}$");
@Override
public boolean isValid(String phone,
ConstraintValidatorContext context) {
return PATTERN.matcher(phone).matches();
}
}
通过分组实现不同场景的差异化校验:
java
public interface CreateGroup {}
public interface UpdateGroup {}
public class Product {
@Null(groups = CreateGroup.class)
@NotNull(groups = UpdateGroup.class)
private Long id;
}
// 在Controller中使用
@PostMapping
public void create(@Validated(CreateGroup.class) Product product)
这种模式在CRUD接口中特别实用,避免在创建和更新时写两套DTO。
默认情况下,校验失败会抛出MethodArgumentNotValidException。我们可以统一处理:
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ResponseStatus(BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidationException(MethodArgumentNotValidException ex) {
return ex.getBindingResult().getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
fieldError -> Optional.ofNullable(fieldError.getDefaultMessage())
.orElse("")
));
}
}
如果项目使用国际化,还可以结合MessageSource获取本地化错误信息。
fail_fast=true
快速返回第一个错误properties
spring.jpa.properties.hibernate.validator.fail_fast=true
良好的数据校验就像建筑物的地基,虽不显眼却至关重要。通过本文介绍的方法,你可以在Spring Boot项目中构建起完善的校验体系。记住:永远不要相信客户端传来的数据,这是后端开发的第一原则。
实际项目中,我推荐将核心校验规则放在领域模型层,这样即使不经过Controller,服务方法调用时也能保证数据合规。你有什么独特的校验经验?欢迎在评论区分享交流。