Java验证框架案例

wen java案例 2

本文目录导读:

Java验证框架案例

  1. Hibernate Validator 完整案例
  2. Spring Boot Validation 案例
  3. 性能优化和最佳实践

我来为您提供几个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验证框架的核心特性和实际应用场景,您可以根据项目需求选择合适的验证方式,并调整错误消息以符合业务要求。

抱歉,评论功能暂时关闭!