1. Parameter verification
In development, you often need to write some code for field verification, such as non-empty fields, field length limits, mailbox format verification, etc. I personally feel that there are two troubles when writing these codes that have little to do with business logic:
hibernate validator (official document) provides a relatively complete and convenient verification implementation method.
The spring-boot-starter-web package has the hibernate-validator package, and there is no need to refer to the hibernate validator dependency.
2. Hibernate validator verification demo
Let’s first look at a simple demo, adding Validator’s annotation:
import org.hibernate.validator.constraints.NotBlank;import javax.validation.constraints.AssertFalse;import javax.validation.constraints.Pattern;
@Getter@Setter@NoArgsConstructorpublic class DemoModel { @NotBlank(message="user name cannot be empty") private String userName; @NotBlank(message="Age cannot be empty") @Pattern(regexp="^[0-9]{1,2}$",message="Age incorrect") private String age; @AssertFalse(message = "must be false") private Boolean isFalse; /** * If it is empty, it will not be checked, and if it is not empty, it will be checked*/ @Pattern(regexp="^[0-9]{4}-[0-9]{2}-[0-9]{2}$",message="The date of birth is incorrect") private String birthday;}POST interface verification, BindingResult is a collection of results that fail to pass the verification:
@RequestMapping("/demo2") public void demo2(@RequestBody @Valid DemoModel demo, BindingResult result){ if(result.hasErrors()){ for (ObjectError error : result.getAllErrors()) { System.out.println(error.getDefaultMessage()); } } } Parameters passed in by POST request :{"userName":"dd","age":120,"isFalse":true,"birthday":"21010-21-12"}
Output result:
The date of birth is incorrect and must be false
Incorrect age
Parameter verification is very convenient. If the annotation + verification on the field is not passed, it can replace the handwriting of a lot of non-empty and field restriction verification codes. Below we have a deeper understanding of how to play parameter verification.
3. Hibernate verification mode
Careful readers must have discovered that in the above example, all sets that fail to pass verification are returned at once. Usually, when the first field does not meet the verification requirements in order, the request can be rejected directly. Hibernate Validator has the following two verification modes:
1. Normal mode (this mode is default)
Normal mode (all attributes will be checked and then all verification failure information will be returned)
2. Fast failure to return to mode
Fast failure return mode (return as long as there is a verification failure)
Two verification mode configuration methods: (refer to the official documentation)
failFast: true Fast Fail Return Mode False Normal Mode
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .failFast( true ) .buildValidatorFactory(); Validator validator = ValidatorFactory.getValidator();
and (hibernate.validator.fail_fast: true Fast Fail Return Mode False Normal Mode)
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .addProperty( "hibernate.validator.fail_fast", "true" ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator();
4. Two types of verifications of hibernate
Configure hibernate Validator to return mode to fast failure:
@Configurationpublic class ValidatorConfiguration { @Bean public Validator validator(){ ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .addProperty( "hibernate.validator.fail_fast", "true" ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); return validator; }}1. Request parameter verification
As in the example in the demo, when verifying the request parameters, add @Valid between the @RequestBody DemoModel demo, and then add BindindResult; for multiple parameters, you can add multiple @Valid and BindingResult, such as:
public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)
@RequestMapping("/demo2") public void demo2(@RequestBody @Valid DemoModel demo, BindingResult result){ if(result.hasErrors()){ for (ObjectError error : result.getAllErrors()) { System.out.println(error.getDefaultMessage()); } } }2. GET parameter verification (@RequestParam parameter verification)
Using the method of checking beans, there is no way to check the content of RequestParam. Generally, when processing Get requests (or fewer parameters), the following code will be used:
@RequestMapping(value = "/demo3", method = RequestMethod.GET) public void demo3(@RequestParam(name = "grade", required = true) int grade,@RequestParam(name = "classroom", required = true) int classroom) { System.out.println(grade + "," + classroom); }Using the @Valid annotation to annotate the parameters corresponding to RequestParam is invalid. The @Validated annotation is required to make the verification take effect. As shown below:
a. At this time, you need to use the Bean of MethodValidationPostProcessor :
@Bean public MethodValidationPostProcessor methodValidationPostProcessor() { /**Default is normal mode, and all verifications will be returned without passing the information collection*/ return new MethodValidationPostProcessor(); } Or you can set the Validator for MethodValidationPostProcessor (because the Validator is not used for verification at this time, the Validator configuration does not work)
@Bean public MethodValidationPostProcessor methodValidationPostProcessor() { MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor(); /**Set validator mode to fast failure return*/ postProcessor.setValidator(validator()); return postProcessor; } @Bean public Validator validator(){ ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .addProperty( "hibernate.validator.fail_fast", "true" ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); return validator; }b. Add annotation to the Controller where the method is located @Validated
@RequestMapping("/validation")@RestController@Validatedpublic class ValidationController { /**If there are only a few objects, just write the parameters to the Controller layer and then verify them in the Controller layer. */ @RequestMapping(value = "/demo3", method = RequestMethod.GET) public void demo3(@Range(min = 1, max = 9, message = "Grade can only be from 1-9") @RequestParam(name = "grade", required = true) int grade, @Min(value = 1, message = "Class minimum can only be 1") @Max(value = 99, message = "Class maximum can only be 99") @RequestParam(name = "classroom", required = true) int classroom) { System.out.println(grade + "," + classroom); }}c. Return to the verification information prompt
You can see: When the verification fails, a ConstraintViolationException exception is thrown and the same catch exception is handled:
@ControllerAdvice@Componentpublic class GlobalExceptionHandler { @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public String handle(ValidationException exception) { if(exception instanceof ConstraintViolationException){ ConstraintViolationException exs = (ConstraintViolationException) exception; Set<ConstraintViolation<?>> violations = exs.getConstraintViolations(); for (ConstraintViolation<?> item : violations) { /**Print the information that fails to pass the verification*/ System.out.println(item.getMessage()); } } return "bad request, " ; }}d. Verification
Browser service request address: http://localhost:8080/validation/demo3?grade=18&classroom=888
The output information when the MethodValidationPostProcessor returned without configuring the fast failure is as follows:
Grades can only be from 1-9
The maximum class can only be 99
When the MethodValidationPostProcessor is configured with the fast failure return, the output information is as follows:
Grades can only be from 1-9
Browser service request address: http://localhost:8080/validation/demo3?grade=0&classroom=0
The output information when the MethodValidationPostProcessor returned without configuring the fast failure is as follows:
Grades can only be from 1-9
The minimum class can only be 1
When the MethodValidationPostProcessor is configured with the fast failure return, the output information is as follows:
Grades can only be from 1-9
3. Model verification
Model to be verified:
@Datapublic class Demo2 { @Length(min = 5, max = 17, message = "length length is between [5,17]") private String length; /**@Size cannot verify Integer, suitable for String, Collection, Map and arrays*/ @Size(min = 1, max = 3, message = "size between [1,3]") private String age; @Range(min = 150, max = 250, message = "range is between [150,250]") private int high; @Size(min = 3,max = 5, message = "The Size of list is in [3,5]") private List<String> list;}Verify the model, all the following verifications are passed:
@Autowired private Validator validator; @RequestMapping("/demo3") public void demo3(){ Demo2 demo2 = new Demo2(); demo2.setAge("111"); demo2.setHigh(150); demo2.setLength("ABCDE"); demo2.setList(new ArrayList<String>(){{add("111"); add("222"); add("333");}}); Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2); for (ConstraintViolation<Demo2> model : violationSet) { System.out.println(model.getMessage()); } }4. Object cascade verification
The object contains another object as a property, and add @Valid to the property to verify the verification inside the object as a property: (When verifying the Demo2 example, you can verify the fields of Demo2)
@Datapublic class Demo2 { @Size(min = 3,max = 5,message = "The Size of the list is in [3,5]") private List<String> list; @NotNull @Valid private Demo3 demo3;}@Datapublic class Demo3 { @Length(min = 5, max = 17, message = "length length is between [5,17]") private String extField;}Cascade verification:
/**Bean is configured with a fast failure return Bean*/ @Autowired private Validator validator; @RequestMapping("/demo3") public void demo3(){ Demo2 demo2 = new Demo2(); demo2.setList(new ArrayList<String>(){{add("111"); add("222"); add("333");}}); Demo3 demo3 = new Demo3(); demo3.setExtField("22"); demo2.setDemo3(demo3); Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2); for (ConstraintViolation<Demo2> model : violationSet) { System.out.println(model.getMessage()); } }The extField field of Demo3 can be checked.
5. Group verification
Conclusion: When verifying the grouping sequence, verify it in the specified grouping order. If the previous verification fails, the subsequent grouping cannot be verified.
There is a scenario where when new user information is added, there is no need to verify userId (because the system is generated); when modifying, the userId needs to verify userId, and the group verification function of the user to validator can be used.
Set validator to normal verification mode ("hibernate.validator.fail_fast", "false"), and use verification GroupA, GroupB and model:
GroupA, GroupB: public interface GroupA {}public interface GroupB {}Verify model: Person
@Datapublic class Person { @NotBlank @Range(min = 1,max = Integer.MAX_VALUE,message = "must be greater than 0",groups = {GroupA.class}) /**User id*/ private Integer userId; @NotBlank @Length(min = 4,max = 20,message = "must be in [4,20]",groups = {GroupB.class}) /**Username*/ private String userName; @NotBlank @Range(min = 0,max = 100,message = "Age must be at [0,100]",groups={Default.class}) /**Age*/ private Integer age; @Range(min = 0,max = 2,message = "Gender must be at [0,2]",groups = {GroupB.class}) /**Gender 0: Unknown; 1: Male; 2: Female*/ private Integer sex;}As shown in Person above, the three groups are respectively validated as follows:
a. Grouping
Only groupings of GroupA and GroupB tags are verified:
@RequestMapping("/demo5")public void demo5(){ Person p = new Person(); /**GroupA verification does not pass */ p.setUserId(-12); /**GroupA verification pass */ //p.setUserId(12); p.setUserName("a"); p.setAge(110); p.setSex(5); Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupA.class, GroupB.class); for (ConstraintViolation<Person> item : validate) { System.out.println(item); }}or
@RequestMapping("/demo6") public void demo6(@Validated({GroupA.class, GroupB.class}) Person p, BindingResult result){ if(result.hasErrors()){ List<ObjectError> allErrors = result.getAllErrors(); for (ObjectError error : allErrors) { System.out.println(error); } } }If GroupA, GroupB, and Default are not validated:
The verification information is as follows:
ConstraintViolationImpl{interpolatedMessage='must be in [4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='must be in [4,20]'}ConstraintViolationImpl{interpolatedMessage='must be greater than 0', propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='must be greater than 0'}ConstraintViolationImpl{interpolatedMessage='Gender must be in [0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='Gender must be in [0,2]'}If GroupA verification is passed, GroupB, and Default verification is not passed:
The verification information is as follows:
ConstraintViolationImpl{interpolatedMessage='must be in [4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='must be in [4,20]'}ConstraintViolationImpl{interpolatedMessage='Gender must be in [0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='Gender must be in [0,2]'}b. Group sequence
In addition to specifying whether to verify by group, you can also specify the verification order of the group. If the verification of the previous group fails, the verification of the next group will not be performed:
Sequence of the specified group (GroupA》GroupB》Default):
@GroupSequence({GroupA.class, GroupB.class, Default.class})public interface GroupOrder {}Test demo:
@RequestMapping("/demo7") public void demo7(){ Person p = new Person(); /**GroupA verification does not pass */ //p.setUserId(-12); /**GroupA verification pass */ p.setUserId(12); p.setUserName("a"); p.setAge(110); p.setSex(5); Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupOrder.class); for (ConstraintViolation<Person> item : validate) { System.out.println(item); } }or
@RequestMapping("/demo8") public void demo8(@Validated({GroupOrder.class}) Person p, BindingResult result){ if(result.hasErrors()){ List<ObjectError> allErrors = result.getAllErrors(); for (ObjectError error : allErrors) { System.out.println(error); } } }If GroupA, GroupB, and Default are not validated:
The verification information is as follows:
ConstraintViolationImpl{interpolatedMessage='must be greater than 0', propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='must be greater than 0'}If GroupA verification is passed, GroupB, and Default verification is not passed:
The verification information is as follows:
ConstraintViolationImpl{interpolatedMessage='must be in [4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='must be in [4,20]'}ConstraintViolationImpl{interpolatedMessage='Gender must be in [0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='Gender must be in [0,2]'}Conclusion: When verifying the grouping sequence, verify it in the specified grouping order. If the previous verification fails, the subsequent grouping cannot be verified.
5. Custom Verifier
Generally speaking, custom verification can solve many problems. But there are also times when the situation cannot be met. At this time, we can implement the validator interface and customize the validator we need.
As shown below, a custom case validator is implemented:
public enum CaseMode { UPPER, LOWER;}@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = CheckCaseValidator.class)@Documentedpublic @interface CheckCase { String message() default ""; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; CaseMode value();}public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> { private CaseMode caseMode; public void initialize(CheckCase checkCase) { this.caseMode = checkCase.value(); } public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { if (s == null) { return true; } if (caseMode == CaseMode.UPPER) { return s.equals(s.toUpperCase()); } else { return s.equals(s.toLowerCase()); } }}Model to verify:
public class Demo{ @CheckCase(value = CaseMode.LOWER,message = "userName must be lowercase") private String userName; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }Validator configuration:
@Bean public Validator validator(){ ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .addProperty( "hibernate.validator.fail_fast", "true" ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); return validator; }Verification test:
@RequestMapping("/demo4") public void demo4(){ Demo demo = new Demo(); demo.setUserName("userName"); Set<ConstraintViolation<Demo>> validate = validator.validate(demo); for (ConstraintViolation<Demo> dem : validate) { System.out.println(dem.getMessage()); } }Output result:
userName must be lowercase
6. Common annotations
The built-in constraint in Bean Validation @Null The annotated element must be null @NotNull The annotated element must not be null @AssertTrue The annotated element must be true @AssertFalse The annotated element must be false @Min(value) The annotated element must be a number, its value must be greater than or equal to the specified minimum value @Max(value) The annotated element must be a number, its value must be less than or equal to the specified maximum value @DecimalMin(value) The annotated element must be a number, its value must be greater than or equal to the specified minimum value @DecimalMax(value) The annotated element must be a number, its value must be less than or equal to the specified maximum value @Size(max=, min=) The size of the annotated element must be within the specified range @Digits (integer, fraction) The annotated element must be a number and its value must be within the acceptable range @Past The annotated element must be a past date @Future The annotated element must be a future date @Pattern(regex=,flag=) The annotated element must meet the specified regular expression Hibernate Validator Attached constraint @NotBlank(message =) The verification string is not null and must be greater than 0 @Email The annotated element must be the email address @Length(min=,max=) The annotated string must be within the specified range @NotEmpty The annotated string must be non-empty @Range(min=,max=,message=) The annotated element must be within the appropriate range //Greater than 0.01, not containing 0.01@NotNull@DecimalMin(value = "0.01", including = false)private Integer greaterThan;//Greater than or equal to 0.01@NotNull@DecimalMin(value = "0.01", including = true)private BigDecimal greatOrEqualThan;@Length(min = 1, max = 20, message = "message cannot be empty")//Cannot use Length as Range//@Range(min = 1, max = 20, message = "message cannot be empty") private String message;
7. Reference materials
References:
http://docs.jboss.org/hibernate/validator/4.2/reference/zh-CN/html_single/#validator-gettingstarted