Data verification is usually done in the coding process, in each layer of the system may be to achieve some verification logic, and then to do business processing. These tedious verification and our business code in a piece will appear bloated. And these checks are usually business independent. I also used Hibernate Validator in my work, but I found that someone didn’t use it well (even see some if else validation code…). So here I decided to clean up the use of Hibernate Validator
Bean Validation 2.0 (JSR 380) defines the metadata model and API for entity and method Validation. Hibernate Validator is the best implementation so far
Use of Hibernate Validator
Rely on
If it is a Spring Boot project, then the Spring-boot-starter -web already relies on hibernate-Validator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Copy the code
In the case of Spring Mvc, you can add hibernate-Validator dependencies directly
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17. The Final</version>
</dependency>
Copy the code
Bean constraint declaration and validation, Validator
Start by adding constraint annotations to our Java objects
@Data
@AllArgsConstructor
public class User {
private String id;
@NotBlank
@Size(max = 20)
private String name;
@NotNull
@Pattern(regexp = "[A-Z][a-z][0-9]")
private String password;
@NotNull
private Integer age;
@Max(10)
@Min(1)
private Integer level;
}
Copy the code
To validate the entity instance, you need to obtain the Validator instance first
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Copy the code
The Validator interface has three methods that can be used to validate the entire entity or just a single attribute of the entity
Validator#validate()
Verify all constraints for all beansValidator#validateProperty()
Validate a single attributeValidator#validateValue()
Checks whether a single attribute of a given class can be successfully validated
public class UserTest {
private static Validator validator;
@BeforeAll
public static void setUpValidator(a) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void validatorTest(a) {
User user = new User(null."".! "" @ # $".null.11);
// Verify all constraints on all beans
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
// Verify a single attribute
Set<ConstraintViolation<User>> constraintViolations2 = validator.validateProperty(user, "name");
// Check whether a single attribute of a given class can be successfully validated
Set<ConstraintViolation<User>> constraintViolations3 = validator.validateValue(User.class, "password"."sa!"); constraintViolations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage())); constraintViolations2.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage())); constraintViolations3.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage())); }}Copy the code
The test results
The value cannot be empty. Maximum value: 10 Match regular expressions"[A-Z][a-z][0-9]"Cannot be null Cannot be null must match a regular expression"[A-Z][a-z][0-9]"
Copy the code
Method constraint declaration and validation, ExecutableValidator
Starting with Bean Validation 1.1, constraints can be applied not only to Javabeans and their properties, but also to the parameters and return values of methods and constructors of any Java type. Here is a brief example
public class RentalStation {
public RentalStation(@NotNull String name) {
/ /...
}
public void rentCar(@NotNull @Future LocalDate startDate, @Min(1) int durationInDays) {
/ /...
}
@NotNull
@Size(min = 1)
public List<@NotNull String> getCustomers() {
/ /...
return null; }}Copy the code
The ExecutableValidator interface performs the validation of method constraints
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();
Copy the code
The ExecutableValidator interface has four methods:
validateParameters()
andvalidateReturnValue()
For method validationvalidateConstructorParameters()
andvalidateConstructorReturnValue()
Used for constructor validation
public class RentalStationTest {
private static ExecutableValidator executableValidator;
@BeforeAll
public static void setUpValidator(a) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();
}
@Test
public void validatorTest(a) throws NoSuchMethodException {
RentalStation rentalStation = new RentalStation("z");
Method method = RentalStation.class.getMethod("rentCar", LocalDate.class, int.class);
Object[] parameterValues = {LocalDate.now().minusDays(1), 0}; Set<ConstraintViolation<RentalStation>> violations = executableValidator.validateParameters( rentalStation, method, parameterValues); violations.forEach(violation -> System.out.println(violation.getMessage())); }}Copy the code
The test results
It needs to be a future time not less than 1Copy the code
Constraint annotations
There are 22 constraint annotations for Validator-api-2.0, as shown in the table below
Empty and non-empty checks
annotations | Support for Java types | instructions |
---|---|---|
@Null | Object | null |
@NotNull | Object | Not null |
@NotBlank | CharSequence | It is not null and must have a non-space character |
@NotEmpty | CharSequence, Collection, Map, and Array | Not null and not null (length/size>0) |
Boolean value to check
annotations | Support for Java types | instructions | note |
---|---|---|---|
@AssertTrue | Boolean, Boolean | To true | Null effectively |
@AssertFalse | Boolean, Boolean | To false | Null effectively |
Date of inspection
annotations | Support for Java types | instructions | note |
---|---|---|---|
@Future | Date, Calendar, Instant, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate, MinguoDate, ThaiBuddhistDate | Verify that the date is after the current time | Null effectively |
@FutureOrPresent | Date, Calendar, Instant, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate, MinguoDate, ThaiBuddhistDate | Verify the date is the current time or later | Null effectively |
@Past | Date, Calendar, Instant, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate, MinguoDate, ThaiBuddhistDate | Verify that the date is before the current time | Null effectively |
@PastOrPresent | Date, Calendar, Instant, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate, MinguoDate, ThaiBuddhistDate | Verify that the date is the current time or before | Null effectively |
Numerical check
annotations | Support for Java types | instructions | note |
---|---|---|---|
@Max | BigDecimal, BigInteger, Byte, short, int, long, and wrapper classes | Less than or equal to | Null effectively |
@Min | BigDecimal, BigInteger, Byte, short, int, long, and wrapper classes | Greater than or equal to | Null effectively |
@DecimalMax | BigDecimal, BigInteger, CharSequence, byte, short, int, long, and wrapper classes | Less than or equal to | Null effectively |
@DecimalMin | BigDecimal, BigInteger, CharSequence, byte, short, int, long, and wrapper classes | Greater than or equal to | Null effectively |
@Negative | BigDecimal, BigInteger, byte, short, int, long, float, double, and wrapper classes | A negative number | Null is valid, and 0 is invalid |
@NegativeOrZero | BigDecimal, BigInteger, byte, short, int, long, float, double, and wrapper classes | Negative or zero | Null effectively |
@Positive | BigDecimal, BigInteger, byte, short, int, long, float, double, and wrapper classes | A positive number | Null is valid, and 0 is invalid |
@PositiveOrZero | BigDecimal, BigInteger, byte, short, int, long, float, double, and wrapper classes | Positive or zero | Null effectively |
@Digits(integer = 3, fraction = 2) | BigDecimal, BigInteger, CharSequence, byte, short, int, long, and wrapper classes | The upper limit of integer and decimal places | Null effectively |
other
annotations | Support for Java types | instructions | note |
---|---|---|---|
@Pattern | CharSequence | Matches the specified regular expression | Null effectively |
CharSequence | Email address | Valid for NULL, default re'*' |
|
@Size | CharSequence, Collection, Map, and Array | Size range (length/size>0) | Null effectively |
Hibernate – Validator Extension Constraints (part)
annotations | Support for Java types | instructions |
---|---|---|
@Length | String | String length range |
@Range | Value type and String | Specified range |
@URL | URL address verification |
Custom constraint annotations
In addition to the constraint annotations provided above (which can be satisfied in most cases), you can customize your own constraint annotations to suit your own needs
There are three steps to defining a custom constraint
- Create constraint annotations
- Implement a validator
- Define the default error message
So let’s go ahead and define a simple annotation to verify the phone number
@Documented @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Constraint(validatedBy = {MobileValidator.class}) @Retention(RUNTIME) @Repeatable(Mobile.List.class) public @interface Mobile {/** * error message() default "Mobile phone number is incorrect "; Class<? >[] groups() default {}; Class<? extends Payload>[] payload() default {}; String regexp () the default "^ 1 ([38] [0-9] 4 [579] | | 5 [0, 3, 5-9] 6 [6] | | 7 [0135678] [89] | 9) \ \ d {8} $"; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) @Documented @interface List { Mobile[] value(); }}Copy the code
Leaving aside the configuration of annotations, custom constraints require the following three properties
message
Error message, can write dead, can also fill in the international keygroups
Grouping information that allows you to specify the validation group to which this constraint belongs (grouping constraints are covered below)payload
Payload. You can use the payload to mark operations that require special treatment
The @REPEATable annotation and the List definition allow the annotation to be repeated multiple times in the same location, usually with different configurations (such as different groups and messages)
@constraint (validatedBy = {mobilevalidator.class}) is the validator that specifies our custom Constraint. Need to implement javax.mail. Validation. ConstraintValidator interface
public class MobileValidator implements ConstraintValidator<Mobile.String> {
/** * Mobile authentication rules */
private Pattern pattern;
@Override
public void initialize(Mobile mobile) {
pattern = Pattern.compile(mobile.regexp());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
returnpattern.matcher(value).matches(); }}Copy the code
The ConstraintValidator interface defines two type parameters that are set in the implementation. The first specifies the annotation class to validate (such as Mobile), and the second specifies the type of element the validator can handle (such as String). The initialize() method accesses the property value of the constraint annotation; The isValid() method is used for validation and returns true to indicate that validation has passed
The Bean validation specification recommends that null values be considered valid. If NULL is not a valid value for the element, the @notnull explicit annotation should be used
So now we have our custom constraint, and we can test it with an example
public class MobileTest {
public void setMobile(@Mobile String mobile){
// to do
}
private static ExecutableValidator executableValidator;
@BeforeAll
public static void setUpValidator(a) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();
}
@Test
public void manufacturerIsNull(a) throws NoSuchMethodException {
MobileTest mobileTest = new MobileTest();
Method method = MobileTest.class.getMethod("setMobile", String.class);
Object[] parameterValues = {"1111111"}; Set<ConstraintViolation<MobileTest>> violations = executableValidator.validateParameters( mobileTest, method, parameterValues); violations.forEach(violation -> System.out.println(violation.getMessage())); }}Copy the code
The mobile phone number is incorrectCopy the code
Grouping constraints
In the custom constraints above, there is a grouping groups attribute is used to specify the validation constraints, we add annotations at as attributes, if there is no grouping configuration information, then the Default will be the Default group javax.mail. Validation. Groups. The Default
The group is defined by the interface and is used as the identifier. Here we create two identifiers, AddGroup and UpdateGroup, to identify new additions and modifications, respectively
public interface AddGroup {
}
public interface UpdateGroup {
}
Copy the code
We then group the ID attribute of our User object
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Null(groups = AddGroup.class)
@NotBlank(groups = UpdateGroup.class)
private String id;
/ /... Other properties are omitted
}
Copy the code
Let’s see how it works. Okay
@Test
public void validatorGroupTest() { User user = new User(); Set<ConstraintViolation<User>> constraintViolations = validator.validatevalue (user.class,"id"."", UpdateGroup.class);
Set<ConstraintViolation<User>> constraintViolations2 = validator.validateValue(User.class, "id"."");
Set<ConstraintViolation<User>> constraintViolations3 = validator.validateValue(User.class, "id"."", AddGroup.class);
constraintViolations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
constraintViolations2.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
constraintViolations3.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
}
Copy the code
The above test validates only when the UpdateGroug group is added, and returns an error message. The following test does not validate constraintViolations2 because the Default group is used. If you want to leave the group unmarked, the Default group will also be verified, and you can inherit the Default group
public interface AddGroup extends Default {}Copy the code
Use the Hibernate Validator in Spring
How do you use a Hibernate Validator in Spring? Or how to use Hibernate Validator in a Web project?
The hibernate validator dependency is added to the Spring-boot-starter -web, which indicates that Spring Boot itself uses the Hibernate Validator framework
Configure the Validator
@Configuration
public class ValidatorConfig {
/** * configure validators **@return validator
*/
@Bean
public Validator validator(a) {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// Fast failure mode
.failFast(true)
// .addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
returnvalidatorFactory.getValidator(); }}Copy the code
You can set fast failure mode to failFast(true) or addProperty(” Hibernate.validator.fail_fast “, “true”). Fast failure mode returns the first invalid parameter during validation. The verification of subsequent parameters is not continued. Otherwise, the system checks all parameters at once and returns all error information that does not meet the requirements
If Spring MVC requires XML configuration, see the configuration below
<mvc:annotation-driven validator="validator"/>
<! -- Validator basic configuration -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<! -- Mapping resource files -->
<property name="validationMessageSource" ref="messageSource"/>
</bean>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource" name="messageSource">
<! --<property name="basenames"> <list> <value>classpath:messages/messages</value> <value>classpath:messages/ValidationMessages</value> </list> </property>-->
<property name="useCodeAsDefaultMessage" value="false" />
<property name="defaultEncoding" value="UTF-8" />
<property name="cacheSeconds" value="60" />
</bean>
Copy the code
Request parameter bean validation
Bean validation on the interface requires either the @Valid or Spring @validated annotations to precede the parameters, both of which result in the application of standard Bean validation. If the validation fails, a BindException is thrown and a 400 (BAD_REQUEST) response is given. Alternatively, validation Errors can be handled locally within the controller through the Errors or BindingResult arguments. In addition, if the parameter before an @ RequestBody annotations, validation error will be thrown MethodArgumentNotValidException anomalies.
@RestController
public class UserController {
@PostMapping("/user")
public R handle(@Valid @RequestBody User user, BindingResult result) {
// Handle validation errors locally within the controller
if (result.hasErrors()) {
result.getAllErrors().forEach(s -> System.out.println(s.getDefaultMessage()));
return R.fail(result.getAllErrors().get(0).getDefaultMessage());
}
// ...
return R.success();
}
@PostMapping("/user2")
public R handle2(@Valid User user, BindingResult result) {
// Handle validation errors locally within the controller
if (result.hasErrors()) {
result.getAllErrors().forEach(s -> System.out.println(s.getDefaultMessage()));
return R.fail(result.getAllErrors().get(0).getDefaultMessage());
}
// ...
return R.success();
}
/ * * * verify by throwing a ` MethodArgumentNotValidException ` * /
@PostMapping("/user3")
public R handle3(@Valid @RequestBody User user) {
// ...
return R.success();
}
/** * failed to throw 'BindException' */
@PostMapping("/user4")
public R handle4(@Valid User user) {
// ...
returnR.success(); }}Copy the code
With Spring’s BindingResult parameter, we can handle validation errors in the controller, but we usually convert the validation error message to our own return format, so it is not necessary to do this in each method. We can do uniform error handling with exceptions that fail validation
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/** * Hibernate validator data binding exception interception **@paramE Binding verification is abnormal *@returnError message */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public R validateErrorHandler(BindException e) {
ObjectError error = e.getAllErrors().get(0);
log.info("Data validation exception: {}", error.getDefaultMessage());
return R.fail(error.getDefaultMessage());
}
/** * Hibernate validator data binding exception interception **@paramE Binding verification is abnormal *@returnError message */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public R validateErrorHandler(MethodArgumentNotValidException e) {
ObjectError error = e.getBindingResult().getAllErrors().get(0);
log.info("Data validation exception: {}", error.getDefaultMessage());
returnR.fail(error.getDefaultMessage()); }}Copy the code
Method parameter validation
configuration
Hibernate Validators can validate parameters at the method level, and Spring implements them as well.
We are in the configuration of the Validator, add MethodValidationPostProcessorBean, in the above ValidatorConfig. Add the Java configuration
/** * Set method parameter validator */
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(a) {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
// Set validator mode to quick failure return
postProcessor.setValidator(validator());
return postProcessor;
}
Copy the code
If it is Spring Mvc, declare the bean information in spring-mVc.xml, otherwise it is invalid in the Controller
<! -- Set method parameter validator -->
<bean id="methodValidationPostProcessor" class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validator" ref="validator"/>
</bean>
Copy the code
use
Configured on MethodValidationPostProcessor above, we can use the method parameters or return values constraint annotations, note that on the classes to use parameter validation must be combined with @ Validated annotations, otherwise is invalid
/** * must add '@Validated` comment * /
@Validated
@RestController
public class UserController {
@GetMapping("/user")
public R handle(@Mobile String mobile) {
// ...
returnR.success(); }}Copy the code
If verification is not through, throws ConstraintViolationException is unusual, in the same way, we can deal with validation errors in global exception handler, add the code in the GlobalExceptionHandler
/** * Spring validator parameter validation exception interception **@paramE Binding verification is abnormal *@returnError message */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public R defaultErrorHandler(ConstraintViolationException e) { Set<ConstraintViolation<? >> violations = e.getConstraintViolations(); ConstraintViolation<? > violation = violations.iterator().next(); log.info("Data validation exception: {}", violation.getMessage());
return R.fail(violation.getMessage());
}
Copy the code
grouping
Spring’s @validate annotation supports group validation
@PostMapping("/user")
public R handle(@Validated(AddGroup.class) @RequestBody User user) {
// ...
return R.success();
}
Copy the code