Spring Data 中的自定义验证器

Spring Data 的验证框架提供了强大的内置验证器,但有时我们需要针对特定​​业务规则自定义验证逻辑。在本文中,我将向您展示如何在 Spring Data 中创建和实现自定义验证器。

了解自定义验证

Spring 中的自定义验证器允许我们定义标准注释(如“@NotNull”或“@Size”)未涵盖的特定验证规则。它们在处理复杂的业务逻辑或特定领域的验证要求时特别有用。

创建自定义验证器

让我们创建一个自定义验证器来检查字符串是否符合特定的业务格式。以下是分步示例:

  • 首先,创建自定义注释:
  • @Documented
    @Constraint(validatedBy = BusinessCodeValidator.class)
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ValidBusinessCode {
        String message() default "Invalid business code format";
        Class[] groups() default {};
        Class[] payload() default {};
    }
  • 实现验证器类:
  • public class BusinessCodeValidator implements ConstraintValidator {
    
        @Override
        public void initialize(ValidBusinessCode constraintAnnotation) {
            // Initialization logic if needed
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if (value == null) {
                return true; // Let @NotNull handle null checking
            }
    
            // Custom validation logic
            return value.matches("^BC-[0-9]{4}-[A-Z]{2}$");
        }
    }
  • 将验证器应用到您的实体:
  • @Entity
    public class Business {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @ValidBusinessCode
        private String businessCode;
    
        // getters and setters
    }

    高级验证功能

    复合验证器

    有时你需要组合多个验证规则。下面介绍如何创建复合验证器:

    @Documented
    @Constraint(validatedBy = CompositeValidator.class)
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ValidBusinessEntity {
        String message() default "Business validation failed";
        Class[] groups() default {};
        Class[] payload() default {};
    }
    
    public class CompositeValidator implements ConstraintValidator {
    
        @Override
        public boolean isValid(Business business, ConstraintValidatorContext context) {
            boolean isValid = true;
    
            if (!isValidBusinessCode(business.getBusinessCode())) {
                context.buildConstraintViolationWithTemplate("Invalid business code")
                       .addPropertyNode("businessCode")
                       .addConstraintViolation();
                isValid = false;
            }
    
            if (!isValidDateRange(business.getStartDate(), business.getEndDate())) {
                context.buildConstraintViolationWithTemplate("Invalid date range")
                       .addPropertyNode("dateRange")
                       .addConstraintViolation();
                isValid = false;
            }
    
            return isValid;
        }
    }

    跨字段验证

    对于涉及多个字段的验证:

    @ValidDateRange
    public class DateRange {
        private LocalDate startDate;
        private LocalDate endDate;
        // getters and setters
    }
    
    public class DateRangeValidator implements ConstraintValidator {
    
        @Override
        public boolean isValid(DateRange range, ConstraintValidatorContext context) {
            if (range.getStartDate() == null || range.getEndDate() == null) {
                return true;
            }
    
            return !range.getStartDate().isAfter(range.getEndDate());
        }
    }

    最佳实践

  • 关注点分离:将验证逻辑隔离在专用的验证器类中。
  • 有意义的信息:提供清晰、用户友好的验证信息:
  • @ValidBusinessCode(message = "Business code must follow format: BC-XXXX-YY")
    private String businessCode;
  • 空处理:在验证器中明确空处理:
  • @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true; // Or false, depending on your requirements
        }
        // validation logic
    }
  • 特定上下文的验证:针对不同的验证上下文使用验证组:
  • public interface CreateValidation {}
    public interface UpdateValidation {}
    
    @ValidBusinessCode(groups = {CreateValidation.class})
    private String businessCode;

    测试自定义验证器

    不要忘记测试你的验证器:

    @Test
    public void testBusinessCodeValidator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
    
        Business business = new Business();
        business.setBusinessCode("invalid-code");
    
        Set> violations = validator.validate(business);
        assertFalse(violations.isEmpty());
        assertEquals("Invalid business code format", 
                     violations.iterator().next().getMessage());
    }

    错误处理

    实现全局异常处理程序来管理验证错误:

    @ControllerAdvice
    public class ValidationExceptionHandler {
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ResponseEntity>> handleValidationErrors(
                MethodArgumentNotValidException ex) {
            List errors = ex.getBindingResult()
                                   .getFieldErrors()
                                   .stream()
                                   .map(FieldError::getDefaultMessage)
                                   .collect(Collectors.toList());
            return new ResponseEntity<>(getErrorsMap(errors), HttpStatus.BAD_REQUEST);
        }
    
        private Map> getErrorsMap(List errors) {
            Map> errorResponse = new HashMap<>();
            errorResponse.put("errors", errors);
            return errorResponse;
        }
    }

    结论

    Spring Data 中的自定义验证器提供了一种实现复杂验证规则的强大方法。通过遵循这些模式和最佳实践,您可以创建可维护、可重用的验证组件,以增强应用程序的数据完整性。

    请记住,要让您的验证器保持专注、经过充分测试并有文档记录。这将使它们更易于维护和在您的应用程序中重复使用。

    这里提供的示例应该为您在 Spring Data 项目中实现自己的自定义验证器奠定坚实的基础。祝您编码愉快!