introduction
Data verification is a core requirement in enterprise-level applications, which ensures the accuracy and consistency of business data.
Spring Validation provides a powerful and flexible data verification framework, which elegantly implements complex verification logic through declarative constraint annotation and group verification mechanisms.
1. Spring Validation infrastructure
1.1 JSR-380 standard integration with Spring
Based on JSR-380 (Bean Validation 2.0), Spring Validation provides a comprehensive data verification solution through seamless integration with Hibernate Validator.
JSR-380 defines the standard constraint annotation and verification API, and Spring extends this standard and provides richer functional support.
This integration allows developers to define verification rules in a declarative manner, greatly simplifying the complexity of data verification.
// Spring Validation dependency configuration/* <dependency> <groupId></groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> */ // Basic configuration to enable verification@Configuration public class ValidationConfig { @Bean public Validator validator() { return ().getValidator(); } @Bean public MethodValidationPostProcessor methodValidationPostProcessor() { return new MethodValidationPostProcessor(); } }
1.2 Verification processing process
The verification process of Spring Validation is completed by multiple core components in collaboration. When an object marked with a constraint annotation is submitted for verification, ValidatorFactory creates a Validator instance, and then iterates through all properties of the object to check whether the constraints are met.
For properties that do not meet the conditions, the corresponding ConstraintViolation is generated, containing violation information and metadata. These violation information can be collected and translated into user-friendly error messages.
// Manual verification example@Service public class ValidationService { @Autowired private Validator validator; public <T> ValidationResult validate(T object) { ValidationResult result = new ValidationResult(); Set<ConstraintViolation<T>> violations = (object); if (!()) { (false); Map<String, String> errorMap = () .collect(( v -> ().toString(), ConstraintViolation::getMessage, (msg1, msg2) -> msg1 + "; " + msg2 )); (errorMap); } return result; } } // Verification result encapsulationpublic class ValidationResult { private boolean valid = true; private Map<String, String> errorMessages = new HashMap<>(); // Getters and setters public boolean hasErrors() { return !valid; } }
2. Detailed explanation of constraint annotations
2.1 Commonly used built-in constraint annotations
Spring Validation provides rich built-in constraint annotations, covering common verification scenarios. These annotations can be divided into several categories: basic verification (such as @NotNull, @NotEmpty), digital verification (such as @Min, @Max), string verification (such as @Size, @Pattern), and time verification (such as @Past, @Future), etc.
Each annotation can customize error messages through the message attribute to improve the user experience. In addition, most annotations also support the association of additional metadata through the payload property.
// Example of built-in constraint annotation@Entity public class Product { @Id @GeneratedValue(strategy = ) private Long id; @NotBlank(message = "Product name cannot be empty") @Size(min = 2, max = 50, message = "Product name length must be between 2-50") private String name; @NotNull(message = "The price cannot be empty") @Positive(message = "The price must be positive") @Digits(integer = 6, fraction = 2, message = "The price format is incorrect") private BigDecimal price; @Min(value = 0, message = "Inventory cannot be negative") private Integer stock; @NotEmpty(message = "Product classification cannot be empty") private List<@NotBlank(message = "Category name cannot be empty") String> categories; @Pattern(regexp = "^[A-Z]{2}\\d{6}$", message = "The product encoding format is incorrect, it should be 2 capital letters + 6 digits") private String productCode; @Email(message = "Contact email format is incorrect") private String contactEmail; @Past(message = "The creation date must be a time passed") private LocalDate createdDate; // Getters and setters }
2.2 Custom constraint annotations
Custom constraint annotations are a powerful solution when built-in constraints cannot meet specific business needs. Creating a custom constraint requires two core components: the constraint annotation definition and the constraint validator implementation. Annotation defines declaration metadata, such as default error messages and application targets; validator implementations contain actual verification logic. By combining existing constraints or implementing new logic, verification rules suitable for any business scenario can be built.
// Example of custom constraint annotation - China Mobile Number Verification@Documented @Constraint(validatedBy = ) @Target({ , }) @Retention() public @interface ChinesePhone { String message() default "Mobile phone number format is incorrect"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } // Constraint Verifier Implementationpublic class ChinesePhoneValidator implements ConstraintValidator<ChinesePhone, String> { private static final Pattern PHONE_PATTERN = ("^1[3-9]\\d{9}$"); @Override public void initialize(ChinesePhone annotation) { // Initialization logic, if required } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { return true; // If non-empty check is required, additional @NotNull should be used } return PHONE_PATTERN.matcher(value).matches(); } } // Use custom constraintspublic class User { @NotNull(message = "The name cannot be empty") private String name; @ChinesePhone private String phoneNumber; // Other fields and methods}
3. In-depth application of group verification
3.1 Basic principles of group verification
Group verification is a powerful feature of Spring Validation, allowing different verification rules to be applied according to different business scenarios. By defining an interface as a packet identifier and specifying the group to which it belongs in the constraint annotation, granular verification control can be achieved. Group verification solves the differentiated verification needs faced by an entity class in different operations (such as adding, modifying, and deleting), and avoids code duplication and maintenance difficulties.
// Basic use of group verification// Define verification groupingpublic interface Create {} public interface Update {} public interface Delete {} // Use grouping constraints@Entity public class Customer { @NotNull(groups = {, }, message = "ID cannot be empty") @Null(groups = , message = "Id should not be specified when creating") private Long id; @NotBlank(groups = {, }, message = "The name cannot be empty") private String name; @NotBlank(groups = , message = "The password cannot be empty when created") private String password; @Email(groups = {, }, message = "The email format is incorrect") private String email; // Getters and setters } // Use group verification in the controller@RestController @RequestMapping("/customers") public class CustomerController { @PostMapping public ResponseEntity<Customer> createCustomer( @Validated() @RequestBody Customer customer) { // Create customer logic return ((customer)); } @PutMapping("/{id}") public ResponseEntity<Customer> updateCustomer( @PathVariable Long id, @Validated() @RequestBody Customer customer) { // Update customer logic return ((id, customer)); } }
3.2 Grouping sequence and sequence verification
For some complex scenarios, group verification may need to be performed in a specific order to ensure that more complex verification is performed only after the basic verification is passed. Spring Validation supports this requirement through a grouping sequence (GroupSequence). Developers can define the execution order of verification groups. Once the verification of a certain group fails, subsequent group verification will be skipped. This mechanism helps improve verification efficiency and provides clearer error feedback.
// Grouping sequence example// Define the basic groupingpublic interface BasicCheck {} public interface AdvancedCheck {} public interface BusinessCheck {} // Define grouping sequence@GroupSequence({, , }) public interface OrderedChecks {} // Use grouping sequence@Entity public class Order { @NotNull(groups = , message = "The order number cannot be empty") private String orderNumber; @NotEmpty(groups = , message = "The line item cannot be empty") private List<OrderItem> items; @Valid // Cascading verification private Customer customer; @AssertTrue(groups = , message = "The total price must match the line item amount") public boolean isPriceValid() { if (items == null || ()) { return true; // Basic checks will capture this problem } BigDecimal calculatedTotal = () .map(item -> ().multiply(new BigDecimal(()))) .reduce(, BigDecimal::add); return (calculatedTotal) == 0; } @AssertTrue(groups = , message = "Insufficient Inventory") public boolean isStockSufficient() { // Inventory check logic return (this); } // Other fields and methods} // Verify using grouping sequence@Service public class OrderService { @Autowired private Validator validator; public ValidationResult validateOrder(Order order) { Set<ConstraintViolation<Order>> violations = (order, ); // Process verification results return processValidationResult(violations); } }
3.3 Cross-field verification and class-level constraints
Some verification rules involve combination logic of multiple fields, such as password matching the confirmation password, starting date earlier than end date, etc. Spring Validation solves this problem through class-level constraints, allowing verification logic to be defined at the class level and handling cross-field rules. This approach is more flexible and powerful than verifying individual fields separately, especially suitable for complex business rules.
// Custom class-level constraint annotations@Target({}) @Retention() @Constraint(validatedBy = ) public @interface ValidDateRange { String message() default "The end date must be later than the start date"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String startDateField(); String endDateField(); } // Class-level constraint validatorpublic class DateRangeValidator implements ConstraintValidator<ValidDateRange, Object> { private String startDateField; private String endDateField; @Override public void initialize(ValidDateRange constraintAnnotation) { = (); = (); } @Override public boolean isValid(Object value, ConstraintValidatorContext context) { try { LocalDate startDate = (LocalDate) (value, startDateField); LocalDate endDate = (LocalDate) (value, endDateField); if (startDate == null || endDate == null) { return true; //Null value verification is handed over to @NotNull for processing } return !(startDate); } catch (Exception e) { return false; } } } // Apply class-level constraints@ValidDateRange( startDateField = "startDate", endDateField = "endDate", groups = ) public class EventSchedule { @NotNull(groups = ) private String eventName; @NotNull(groups = ) private LocalDate startDate; @NotNull(groups = ) private LocalDate endDate; // Other fields and methods}
4. Practical application and best practices
4.1 Controller parameter verification
The integration of Spring MVC and Spring Validation provides convenient controller parameter verification. Spring automatically validates the requested data by adding @Valid or @Validated annotations to the Controller method parameters. Combining the BindingResult parameter, verification errors can be captured and customized. For the RESTful API, you can use the global exception handler to uniformly handle verification exceptions and return standardized error responses.
// Example of controller parameter verification@RestController @RequestMapping("/api/products") public class ProductController { @Autowired private ProductService productService; // Request verification @PostMapping public ResponseEntity<?> createProduct( @Validated() @RequestBody Product product, BindingResult bindingResult) { if (()) { Map<String, String> errors = ().stream() .collect(( FieldError::getField, FieldError::getDefaultMessage, (msg1, msg2) -> msg1 + "; " + msg2 )); return ().body(errors); } return ((product)); } // Verification of path variables and request parameters @GetMapping("/search") public ResponseEntity<?> searchProducts( @RequestParam @NotBlank String category, @RequestParam @Positive Integer minPrice, @RequestParam @Positive Integer maxPrice) { return ( (category, minPrice, maxPrice) ); } } // Global exception handling@RestControllerAdvice public class ValidationExceptionHandler { @ExceptionHandler() public ResponseEntity<Object> handleValidationExceptions( MethodArgumentNotValidException ex) { Map<String, String> errors = ().getFieldErrors().stream() .collect(( FieldError::getField, FieldError::getDefaultMessage, (msg1, msg2) -> msg1 + "; " + msg2 )); return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body(new ApiError("Validation Failed", errors)); } @ExceptionHandler() public ResponseEntity<Object> handleConstraintViolation( ConstraintViolationException ex) { Map<String, String> errors = ().stream() .collect(( violation -> ().toString(), ConstraintViolation::getMessage, (msg1, msg2) -> msg1 + "; " + msg2 )); return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body(new ApiError("Validation Failed", errors)); } }
Summarize
Spring Validation provides strong data verification support for enterprise-level applications through standardized constraint annotations and flexible group verification mechanisms.
The declarative nature of constraint annotations simplifies verification code, while custom constraints meet a variety of specific business needs. Grouping checksum grouping sequences solve the differentiated verification problem in different scenarios, while class-level constraints implement complex cross-field verification logic.
In practical applications, combining controller parameter checking and global exception handling, a robust and easy-to-use verification system can be built.
The above is personal experience. I hope you can give you a reference and I hope you can support me more.