本文目录导读:

我来为您提供几个Java验证框架的完整案例,主要涵盖Hibernate Validator(最常用)和Spring Boot Validation。
Hibernate Validator 完整案例
Maven依赖
<dependencies>
<!-- Hibernate Validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<!-- 表达式语言支持 -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
</dependency>
<!-- JUnit 5 用于测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
实体类与验证注解
import jakarta.validation.constraints.*;
import jakarta.validation.Valid;
import java.time.LocalDate;
import java.util.List;
public class User {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100, message = "密码长度至少6位")
private String password;
@Email(message = "邮箱格式不正确")
@NotBlank(message = "邮箱不能为空")
private String email;
@NotNull(message = "年龄不能为空")
@Min(value = 18, message = "年龄必须大于18岁")
@Max(value = 130, message = "年龄不能超过130岁")
private Integer age;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phoneNumber;
@Past(message = "出生日期必须是过去的日期")
private LocalDate birthDate;
@DecimalMin(value = "0.0", message = "余额不能为负数")
@DecimalMax(value = "999999.99", message = "余额超出范围")
private Double balance;
@Valid
private Address address;
@Size(min = 1, message = "至少要有一个联系电话")
@Valid
private List<Phone> phones;
// getters and setters 省略
}
class Address {
@NotBlank(message = "省份不能为空")
private String province;
@NotBlank(message = "城市不能为空")
private String city;
@NotBlank(message = "详细地址不能为空")
private String detail;
}
class Phone {
@NotBlank(message = "电话号码不能为空")
@Pattern(regexp = "^\\d{3,4}-\\d{7,8}$", message = "固定电话格式不正确")
private String number;
@NotNull(message = "电话类型不能为空")
private String type;
}
自定义验证注解
// 自定义注解 - 验证强密码
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = StrongPasswordValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface StrongPassword {
String message() default "密码必须包含大小写字母、数字和特殊字符";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 验证器实现
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {
@Override
public void initialize(StrongPassword strongPassword) {
// 初始化逻辑
}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
if (password == null) {
return true; // 让 @NotBlank 处理空值
}
boolean hasUpper = password.chars().anyMatch(Character::isUpperCase);
boolean hasLower = password.chars().anyMatch(Character::isLowerCase);
boolean hasDigit = password.chars().anyMatch(Character::isDigit);
boolean hasSpecial = password.chars().anyMatch(ch -> !Character.isLetterOrDigit(ch));
boolean hasLength = password.length() >= 8;
return hasUpper && hasLower && hasDigit && hasSpecial && hasLength;
}
}
// 使用自定义注解
public class UserRegistration {
@NotBlank(message = "密码不能为空")
@StrongPassword
private String password;
}
验证服务
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.util.Set;
import java.util.stream.Collectors;
public class ValidationService {
private final Validator validator;
public ValidationService() {
// 获取验证器
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
this.validator = factory.getValidator();
}
// 基础验证方法
public <T> Set<ConstraintViolation<T>> validate(T object) {
return validator.validate(object);
}
// 批量验证并返回错误消息
public <T> List<String> validateAndGetMessages(T object) {
Set<ConstraintViolation<T>> violations = validator.validate(object);
return violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
}
// 按分组验证
public <T> Set<ConstraintViolation<T>> validateByGroup(T object, Class<?> group) {
return validator.validate(object, group);
}
// 验证单个属性
public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName) {
return validator.validateProperty(object, propertyName);
}
}
测试案例
import jakarta.validation.ConstraintViolation;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Set;
public class UserValidationTest {
private static ValidationService validationService;
@BeforeAll
public static void setup() {
validationService = new ValidationService();
}
@Test
public void testInvalidUser() {
User user = new User();
user.setUsername("ab"); // 太短
user.setPassword("short"); // 太短
user.setEmail("invalid-email"); // 格式错误
user.setAge(15); // 太小
Set<ConstraintViolation<User>> violations = validationService.validate(user);
for (ConstraintViolation<User> violation : violations) {
System.out.println("字段: " + violation.getPropertyPath()
+ ", 错误: " + violation.getMessage());
}
assert (!violations.isEmpty());
}
@Test
public void testValidUser() {
User user = new User();
user.setUsername("testUser123");
user.setPassword("strongPass123");
user.setEmail("test@example.com");
user.setAge(25);
user.setPhoneNumber("13812345678");
user.setBalance(1000.50);
user.setBirthDate(LocalDate.of(1995, 1, 1));
// 设置地址
Address address = new Address();
address.setProvince("广东省");
address.setCity("深圳市");
address.setDetail("南山区科技园");
user.setAddress(address);
Set<ConstraintViolation<User>> violations = validationService.validate(user);
assert (violations.isEmpty());
}
@Test
public void testCustomPassword() {
UserRegistration registration = new UserRegistration();
registration.setPassword("weak");
Set<ConstraintViolation<UserRegistration>> violations = validationService.validate(registration);
for (ConstraintViolation<UserRegistration> violation : violations) {
System.out.println("错误: " + violation.getMessage());
}
assert (!violations.isEmpty());
}
}
Spring Boot Validation 案例
Spring Boot 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Controller层验证
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import java.time.LocalDate;
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
// 请求体验证
@PostMapping("/register")
public ApiResponse register(@Valid @RequestBody RegisterRequest request) {
// 验证通过后的业务逻辑
return ApiResponse.success("注册成功");
}
// 路径参数验证
@GetMapping("/{userId}")
public User getUser(@PathVariable @Min(1) @NotNull Long userId) {
// 业务逻辑
return userService.getUserById(userId);
}
// 查询参数验证
@GetMapping("/search")
public List<User> searchUsers(
@RequestParam @Email String email,
@RequestParam @Min(18) @Max(130) Integer age,
@RequestParam @Pattern(regexp = "^[a-zA-Z]+$") String name) {
// 业务逻辑
return userService.searchUsers(email, age, name);
}
}
// 请求DTO
public class RegisterRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20)
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名格式不正确")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 20, message = "密码长度必须在8-20之间")
@StrongPassword
private String password;
@Email(message = "邮箱格式不正确")
@NotBlank(message = "邮箱不能为空")
private String email;
@NotNull(message = "生日不能为空")
@Past(message = "生日必须是过去的日期")
private LocalDate birthday;
@Valid
private AddressDTO address;
// getters and setters
}
全局异常处理
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理RequestBody验证异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest()
.body(ApiResponse.error("验证失败", errors));
}
// 处理参数和路径变量验证异常
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleConstraintViolation(
ConstraintViolationException ex) {
Map<String, String> errors = new HashMap<>();
ex.getConstraintViolations().forEach(cv -> {
String path = cv.getPropertyPath().toString();
String message = cv.getMessage();
errors.put(path, message);
});
return ResponseEntity.badRequest()
.body(ApiResponse.error("参数验证失败", errors));
}
// 统一响应格式
public static class ApiResponse<T> {
private boolean success;
private String message;
private T data;
// 构造器、getters/setters
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(true);
response.setData(data);
return response;
}
public static <T> ApiResponse<T> error(String message, T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(false);
response.setMessage(message);
response.setData(data);
return response;
}
}
}
Service层验证
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
@Service
@Validated
public class UserService {
// 方法参数验证
public User createUser(@Valid @NotNull User user) {
return userRepository.save(user);
}
// 方法参数验证
public User updateUserEmail(
@Min(1) Long userId,
@Email @NotBlank String newEmail) {
User user = userRepository.findById(userId).orElseThrow();
user.setEmail(newEmail);
return userRepository.save(user);
}
// 返回值验证(启动后每次返回都会验证)
public @Valid User getUserById(@Min(1) Long userId) {
return userRepository.findById(userId).orElseThrow();
}
}
分组验证案例
// 定义分组接口
public interface CreateGroup {}
public interface UpdateGroup {}
// 使用分组
public class User {
@Null(groups = CreateGroup.class, message = "创建时ID必须为空")
@NotNull(groups = UpdateGroup.class, message = "更新时ID不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
private String username;
@Email
private String email;
}
性能优化和最佳实践
手动验证优化
public class OptimizedValidationService {
private static final Validator validator;
static {
// 使用缓存和初始化配置
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true) // 快速失败,只返回第一个错误
.buildValidatorFactory();
validator = factory.getValidator();
}
// 使用 @Validated 和 Spring 的依赖注入
@Autowired
public OptimizedValidationService(Validator validator) {
// 使用Spring管理的Validator实例
}
// 构建器模式创建对象时验证
public static class UserBuilder {
private User user;
public UserBuilder validateOnBuild() {
Set<ConstraintViolation<User>> violations = validator.validate(user);
if (!violations.isEmpty()) {
String errors = violations.stream()
.map(v -> v.getPropertyPath() + ": " + v.getMessage())
.collect(Collectors.joining(", "));
throw new ValidationException(errors);
}
return this;
}
}
}
自定义约束组合
// 组合多个约束
@NotBlank(message = "手机号不能为空")
@Pattern(
regexp = "^1[3-9]\\d{9}$",
message = "手机号格式不正确"
)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {})
public @interface ChinesePhoneNumber {
String message() default "手机号不合法";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
条件验证
// 条件验证示例 - 使用自定义验证器
public class ConditionalValidator {
// 根据性别验证不同规则
public static class GenderConstraintValidator
implements ConstraintValidator<ValidPerson, Person> {
@Override
public void initialize(ValidPerson constraintAnnotation) {}
@Override
public boolean isValid(Person person, ConstraintValidatorContext context) {
if (person.getGender() == null) {
return false;
}
if ("MALE".equals(person.getGender())) {
return person.getAge() >= 22; // 男性结婚年龄
} else if ("FEMALE".equals(person.getGender())) {
return person.getAge() >= 20; // 女性结婚年龄
}
return false;
}
}
}
这个案例涵盖了Java验证框架的核心特性和实际应用场景,您可以根据项目需求选择合适的验证方式,并调整错误消息以符合业务要求。