Spring Validator Study Guide: Mastering Object Verification from Zero
1. The role of the Validator interface: your data "goalkeeper"
Imagine that you have developed a user registration function, and the data submitted by the user may have various problems: the name is not filled in, the age is written as a negative number... If these errors are saved directly to the database, it will lead to errors in the subsequent process.ValidatorJust like a strict goalkeeper, checking whether each field complies with the rules before data enters the system.
Core mission:
- Check whether the object attribute is legal (such as non-number, numerical range).
- Collect error information to facilitate subsequent prompts for users.
2. Two major methods of Validator interface: How to work?
1. supports(Class clazz)
: Can I handle this object?
- effect: Determine whether Validator currently supports verification of objects of a certain class.
-
Key Choices:
-
Exact match:
return (clazz);
→ Verify onlyPerson
kind. -
Flexible matching:
return (clazz);
→ SupportPerson
and its subclasses.
-
Exact match:
Sample Scenario:
- If you have one
Student extends Person
,useequals
hour,Student
The object will not be verified;isAssignableFrom
Then it will be checked.
2. validate(Object target, Errors errors)
: Perform verification!
-
effect: Write specific verification rules and record them when an error is found
Errors
Object. -
Common tools:
ValidationUtils
Simplify non-empty checks.
Sample code:
public void validate(Object target, Errors errors) { // Check whether name is empty (errors, "name", ""); Person person = (Person) target; // Check whether the age is legal if (() < 0) { ("age", "", "Age cannot be negative!"); } }
3. Handle nested objects: How to avoid duplicate code?
Suppose you have oneCustomer
Class, includingAddress
Object:
public class Customer { private String firstName; private String surname; private Address address; // Nested objects}
Problem: Verify all fields directly in one Validator
shortcoming:
- If other categories (such as
Order
) also includesAddress
, the address verification code needs to be written repeatedly. - Maintenance difficulty: When modifying address rules, multiple codes need to be changed.
Correct way to do it:
Split Validator and use it in combination!
Step 1: Create an independent Validator for each class
- AddressValidator(Check address):
public class AddressValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return (clazz); } @Override public void validate(Object target, Errors errors) { (errors, "street", ""); (errors, "city", ""); } }
CustomerValidator(Check the client and reuse the AddressValidator):
public class CustomerValidator implements Validator { private final Validator addressValidator; // Inject AddressValidator through constructor public CustomerValidator(Validator addressValidator) { = addressValidator; } @Override public boolean supports(Class<?> clazz) { return (clazz); } @Override public void validate(Object target, Errors errors) { // 1. Verify the client's direct field (firstName, incident) (errors, "firstName", ""); (errors, "surname", ""); Customer customer = (Customer) target; Address address = (); // 2. Verify nested Address objects if (address == null) { ("address", ""); return; } // 3. Key: Switch the wrong path to "address" to prevent field name conflicts ("address"); try { (addressValidator, address, errors); } finally { (); // Restore the original path } } }
Step 2: Actual use
// Create ValidatorAddressValidator addressValidator = new AddressValidator(); CustomerValidator customerValidator = new CustomerValidator(addressValidator); // Prepare test dataCustomer customer = new Customer(); (""); // Empty name(new Address()); // Empty address// Perform verificationErrors errors = new BeanPropertyBindingResult(customer, "customer"); (customer, errors); // Output errorif (()) { ().forEach(error -> { ("Field:" + () + "." + ()); }); } // Output result:// Fields:// Fields:
4. Key Skills and Frequently Asked Questions
1. Error path management
pushNestedPath
andpopNestedPath
:
Make sure the error fields of nested objects are prefixed (e.g.) to avoid conflicts with the field name of the main object.
2. Defensive programming
When combining Validator, check whether the injected Validator supports the target type:
public CustomerValidator(Validator addressValidator) { if (!()) { throw new IllegalArgumentException("The Address type must be supported!"); } = addressValidator; }
3. International support
Error code (such as) can correspond to language resource files (such as
messages_zh.properties
), implement multilingual prompts:
# messages_zh.properties =The name cannot be empty =Street address cannot be empty
5. Summary: Why is it designed like this?
-
Code reuse:
AddressValidator
Can be verified by other classes (such asOrder
、Company
) Use directly. - Single Responsibility: Each Validator is only responsible for the verification of one class, with clear logic and easy to maintain.
-
Flexible expansion: Add a new nested object (such as
PaymentInfo
) When you just create a new Validator and inject it, no need to modify the existing code.
3.2. Parsing the error code into error message: In-depth analysis and instance demonstration
1. Core concept: Multi-level analysis of error codes
When you call in SpringrejectValue
When the method registration error is wrong (for example, checking the user's age is illegal), Spring will not only record the single error code you specified, butAutomatically generate a set of hierarchical error codes. This design allows developers to flexibly define error messages through different levels of error codes to achieve a "specific to general" coverage strategy.
2. Error code generation rules
Assume thatPersonValidator
The following verification logic is triggered:
("age", "");
Generated error code(From high to low by priority):
-
→ Field name + error code + field type
-
→ Field name + error code
-
→ Original error code
3. Matching strategy for message resource files
Spring'sMessageSource
Will follow the priority order of the error code in the message resource file (egFind the corresponding message in )Once a match is found, stop searching immediately。
Sample message resource file:
# =Age must be an integer and not exceed 120 age =Can't exceed the age 120 age =The input value is unreasonable
Matching process:
- Priority search
→ Use if it exists.
- If not found, look up
→ Use if it exists.
- Last search
→ As a bottom-up message.
4. Practical demonstration: From code to error message
Step 1: Create entity class and verification device
// public class Person { private String name; private int age; // getters/setters } // public class PersonValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return (clazz); } @Override public void validate(Object target, Errors errors) { Person person = (Person) target; if (() > 120) { ("age", ""); } } }
Step 2: Configure the message resource file
existsrc/main/resources/
Definition in:
# Specific to field type=Age must be an integer and cannot exceed 120 age # To specific fields=Can't exceed the age 120 age # General error=The entered value is invalid
Step 3: Write the test code
@SpringBootTest public class ValidationTest { @Autowired private MessageSource messageSource; @Test public void testAgeValidation() { Person person = new Person(); (150); // Trigger an error Errors errors = new BeanPropertyBindingResult(person, "person"); PersonValidator validator = new PersonValidator(); (person, errors); // Extract error message ("age").forEach(error -> { String message = ((), null, ()); ("Error message:" + message); }); } }
Output result:
Error message: Age must be an integer and cannot be older than 120 years old
Analysis:
becauseIf it exists in the message file, use the message first. If you delete this line,It will match
, and so on.
5. Custom error code generation strategy
DefaultDefaultMessageCodesResolver
The generated code format is:Error code + field name + field type
。
If you need to modify the rules, you can customize itMessageCodesResolver
。
Example: Simplify error code
@Configuration public class ValidationConfig { @Bean public MessageCodesResolver messageCodesResolver() { DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver(); (.POSTFIX_ERROR_CODE); return resolver; } }
Effect:
CallrejectValue("age", "")
The generated code becomes:
6. Frequently Asked Questions and Solutions
Question 1: How to view the actual generated error code?
Print the error object in the test code:
("age").forEach(error -> { ("Error Code List:" + (())); });
Output:
Error code list: [, , ]
Question 2: How to represent field types in code?
Spring uses a simple class name for a field (e.g.int
、String
). For custom types (e.g.Address
), will be used in the codeaddress
(Class name lowercase).
7. Summary: Why do we need to hierarchically Error Codes?
- Flexible coverage: Allows customization of messages for specific fields or types, and provides a universal guarantee.
- International friendly: Different languages can define messages at different levels without modifying code.
- Code decoupling: Separate the verification logic from specific error messages to improve maintainability.
Study Suggestions:
- Observation through debugging
()
The output of the code generation rules are in-depth. - Priority to using field-level error codes in projects (e.g.
) to improve the accuracy of error messages.
This is the article about Spring Validator Learning Guide: This is the end of this article about mastering object verification from scratch. For more related Spring Validator object verification content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!