Hello, I am Misty.
A few days ago, I wrote an article titled “How SpringBoot unified the backend return format? In SprinBoot, we’ll look at how to integrate a parameter Validator with SprinBoot, as well as some advanced techniques for parameter validation (custom validation, grouping validation).
This article relies on the code base of the previous article and has included a global exception verifier in the project. (Code repository at the end of the article)
What is a Validator and why is it needed?
Why is parameter verification required
In daily interface development, you need to verify interface parameters to prevent services from being affected by invalid parameters. For example, you need to verify whether the user name and password are empty when you log in to the system and whether the email and mobile phone number format is correct when you create a user. It is too tedious to check interface parameters one by one by code, and the code readability is poor.
The Validator framework is designed to reduce the need for developers to write less code and improve development efficiency. Validators are used to validate interface parameters, such as mandatory validation, email validation, user names that must be between 6 and 12, etc.
The Validator framework follows the JSR-303 validation Specification, which stands for Java Specification Requests.
Let’s take a look at how to integrate the parameter validation framework in SpringbBoot.
Integrated parameter verification in SpringBoot
The first step is to introduce dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Copy the code
Note: Since Springboot-2.3, validation packages are separate as a starter component, so validation and The Web are required, whereas prior to Springboot-2.3, web dependencies are required.
The second step is to define the entity class for parameter validation
@Data
public class ValidVO {
private String id;
@length (min = 6, Max = 12,message = "appId Length must be between 6 and 12 ")
private String appId;
@notblank (message = "name required ")
private String name;
@email (message = "Please fill in the correct Email address ")
private String email;
private String sex;
@notempty (message = "level cannot be empty ")
private String level;
}
Copy the code
In the actual development, the corresponding business prompt, namely message attribute, needs to be set for the fields to be verified.
Common constraints are annotated as follows:
annotations | function |
---|---|
@AssertFalse | Can be null, but must be false if not |
@AssertTrue | May be null, but must be true if not |
@DecimalMax | The value cannot exceed the maximum value |
@DecimalMin | The setting cannot exceed the minimum value |
@Digits | The setting must be numeric and the number of digits of the numeric integer and the number of digits of the decimal must be within the specified range |
@Future | Date must be in the future of the current date |
@Past | The date must be past the current date |
@Max | The maximum value must not exceed this value |
@Min | The maximum must not be less than the minimum |
@NotNull | It cannot be null, but can be null |
@Null | Must be null |
@Pattern | The specified regular expression must be satisfied |
@Size | Size () values for collections, arrays, maps, and so on must be within the specified range |
The value must be in email format | |
@Length | The length must be within the specified range |
@NotBlank | The string must not be null, and the string trim() must not equal “. |
@NotEmpty | The value cannot be null, and the size() of collections, arrays, and maps cannot be 0. The string trim() can be equal to “” |
@Range | Values must be within the specified range |
@URL | It must be a URL |
Note: This table is only a simple description of the annotation function. It does not explain the attributes of each annotation. See the source code.
Third, define validation classes for testing
@RestController
@Slf4j
@Validated
public class ValidController {
@ ApiOperation (" RequestBody calibration ")
@PostMapping("/valid/test1")
public String test1(@Validated @RequestBody ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test1 valid success";
}
@ ApiOperation (" the Form validation ")
@PostMapping(value = "/valid/test2")
public String test2(@Validated ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test2 valid success";
}
@apiOperation (" Single parameter verification ")
@PostMapping(value = "/valid/test3")
public String test3(@Email String email){
log.info("email is {}", email);
return "email valid success"; }}Copy the code
Test1 uses the @requestBody annotation to accept JSON data sent from the front end, test2 simulates a form submission, and test3 simulates a single-parameter submission. Note that the “@” annotation must be added to the Controller when single-parameter verification is used. Otherwise, the verification will not take effect.
Step 4: Experience the effect
- Call the test1 method, prompting
org.springframework.web.bind.MethodArgumentNotValidException
abnormal
POST http://localhost:8080/valid/test1
Content-Type: application/json
{
"id": 1."level": "12"."email": "47693899"."appId": "ab1c"
}
Copy the code
{
"status": 500."message": "Validation failed for argument [0] in public java.lang.String com.jianzh5.blog.valid.ValidController.test1(com.jianzh5.blog.valid.ValidVO) with 3 errors: [Field error in object 'validVO' on field 'email': rejected value [47693899]; codes [Email.validVO.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.email,email]; arguments []; default message [email], [Ljavax. Validation. Constraints. The Pattern $Flag; @ 26139123. *]; the default message [] is not a legitimate E-mail address]..."."data": null."timestamp": 1628239624332
}
Copy the code
- Call the test2 method, prompting
org.springframework.validation.BindException
abnormal
POST http://localhost:8080/valid/test2
Content-Type: application/x-www-form-urlencoded
id=1&level=12&email=476938977&appId=ab1c
Copy the code
{
"status": 500."message": "org.springframework.validation.BeanPropertyBindingResult: 3 errors\nField error in object 'validVO' on field 'name': rejected value [null]; codes [NotBlank.validVO.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.name,name]; arguments []; default message [name]]; Default message [mandatory name]..."."data": null."timestamp": 1628239301951
}
Copy the code
- Call the test3 method, prompting
javax.validation.ConstraintViolationException
abnormal
POST http://localhost:8080/valid/test3
Content-Type: application/x-www-form-urlencoded
email=476938977
Copy the code
{
"status": 500."message": "Test3. email: Not a valid email address"."data": null."timestamp": 1628239281022
}
Copy the code
The Validator framework helps automate validation of parameters.
Parameter exceptions are added to the global exception handler
Although we defined a global exception interceptor and saw that it did work, the error message returned by the Validator framework was too bloated to be easy to read, and we needed to simplify it for the sake of front-end prompts.
Modify RestExceptionHandler directly to separate the three exceptions to parameter validation: Javax.mail. Validation. ConstraintViolationException, org. Springframework. Validation. BindException, Org. Springframework. Web. Bind. MethodArgumentNotValidException, code is as follows:
@ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
public ResponseEntity<ResultData<String>> handleValidatedException(Exception e) {
ResultData<String> resp = null;
if (e instanceof MethodArgumentNotValidException) {
// BeanValidation exception
MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
ex.getBindingResult().getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("; "))); }else if (e instanceof ConstraintViolationException) {
// BeanValidation GET simple param
ConstraintViolationException ex = (ConstraintViolationException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
ex.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "))); }else if (e instanceof BindException) {
// BeanValidation GET object param
BindException ex = (BindException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
ex.getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("; "))); }return new ResponseEntity<>(resp,HttpStatus.BAD_REQUEST);
}
Copy the code
Experience the effect
POST http://localhost:8080/valid/test1
Content-Type: application/json
{
"id": 1."level": "12"."email": "47693899"."appId": "ab1c"
}
Copy the code
{
"status": 400."message": "Name is mandatory; Not a legitimate email address; AppId must be between 6 and 12"."data": null."timestamp": 1628435116680
}
Copy the code
Don’t you feel cleaner?
User-defined parameter verification
Although the annotations provided by Spring Validation are generally sufficient, we need to define our own annotations to implement automatic Validation in the face of complex definitions.
For example, the above entity class sex sex attribute, only allow the front end to pass M, F these two enumerated values, how to achieve it?
The first step is to create custom annotations
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumString.List.class)
@Documented
@Constraint(validatedBy = EnumStringValidator.class)// Specify which class performs the validation logic
public @interface EnumString {
String message(a) default "value not in enum values."; Class<? >[] groups()default {};
Class<? extends Payload>[] payload() default {};
/ * * *@return date must in this value array
*/
String[] value();
/**
* Defines several {@link EnumString} annotations on the same element.
*
* @see EnumString
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interfaceList { EnumString[] value(); }}Copy the code
Step 2: Customize the verification logic
public class EnumStringValidator implements ConstraintValidator<EnumString.String> {
private List<String> enumStringList;
@Override
public void initialize(EnumString constraintAnnotation) {
enumStringList = Arrays.asList(constraintAnnotation.value());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(value == null) {return true;
}
returnenumStringList.contains(value); }}Copy the code
Third, add annotations to the field
@apiModelProperty (value = "gender ")
@enumString (value = {"F","M"}, message=" only F or M")
private String sex;
Copy the code
Step 4: Experience the effect
POST http://localhost:8080/valid/test2
Content-Type: application/x-www-form-urlencoded
id=1&name=javadaily&level=12&email=476938977@qq.com&appId=ab1cdddd&sex=N
Copy the code
{
"status": 400."message": "Only F or M allowed."."data": null."timestamp": 1628435243723
}
Copy the code
Packet check
A VO object has fields that are mandatory when it is added and optional when it is updated. In ValidVO, the id and appId attributes are optional for new operations, and mandatory for editing operations. Name is mandatory for new operations.
In actual development, I have seen many students create two VO objects, ValidCreateVO and ValidEditVO, to handle this scenario. This can also achieve the effect, but it will cause class bloat, and it is extremely easy to be laughed at by veteran developers.
The Validator framework already takes this scenario into account and provides a solution that many of you don’t know is group validation. To use packet verification, there are only three steps:
Step 1: Define the packet interface
public interface ValidGroup extends Default {
interface Crud extends ValidGroup{
interface Create extends Crud{}interface Update extends Crud{}interface Query extends Crud{}interface Delete extends Crud{}}}Copy the code
Here we define a packet interface ValidGroup let its inheritance javax.mail. Validation. Groups. Default, interface in groups defined in different type of operation, Create, Update, Query, Delete. We’ll talk about why you need to inherit Default later.
The second step is to assign groups to parameters in the model
@Data
@apiModel (value = "ApiModel ")
public class ValidVO {
@ApiModelProperty("ID")
@Null(groups = ValidGroup.Crud.Create.class)
@ NotNull (groups = ValidGroup. Crud. Update. Class, message = "application ID can't be empty")
private String id;
@Null(groups = ValidGroup.Crud.Create.class)
@ NotNull (groups = ValidGroup. Crud. Update. Class, message = "application ID can't be empty")
@apiModelProperty (value = "ID",example = "cloud")
private String appId;
@apiModelProperty (value = "name ")
@ NotBlank (groups = ValidGroup. Crud. Create the class, message = "name for mandatory")
private String name;
@apiModelProperty (value = "mailbox ")
@email (message = "Please fill in the correct Email address ")privte String email; . }Copy the code
Parameters are grouped, and default groups are used for those that are not.
Third, assign groups to methods that require parameter validation
@RestController
@api (" parameter verification ")
@Slf4j
@Validated
public class ValidController {
@ ApiOperation (" new ")
@PostMapping(value = "/valid/add")
public String add(@Validated(value = ValidGroup.Crud.Create.class) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test3 valid success";
}
@ ApiOperation (" update ")
@PostMapping(value = "/valid/update")
public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test4 valid success"; }}Copy the code
Here we specify Create and update groups for the add() and update() methods using the value attribute.
Step 4: Experience the effect
POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded
name=javadaily&level=12&email=476938977@qq.com&sex=F
Copy the code
We passed the id and appId parameters when creating.
A parameter verification error occurs when we call the update method with the same parameters.
{
"status": 400."message": "ID cannot be empty; Application ID cannot be empty"."data": null."timestamp": 1628492514313
}
Copy the code
Since email belongs to the Default group, and our grouping interface ValidGroup inherits the Default group, we can also verify the email field parameter. Such as:
POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded
name=javadaily&level=12&email=476938977&sex=F
Copy the code
{
"status": 400."message": "Please fill in the valid email address"."data": null."timestamp": 1628492637305
}
Copy the code
Of course, if your ValidGroup does not inherit the Default group, It will need to add @ Validated on code attribute (value = {ValidGroup. Crud. Create. Class, Default. Class} to make effective email field calibration.
summary
Parameter calibration in the actual development use frequency is very high, but many students also only stay in the use of simple, like a group check, custom parameters calibration basic didn’t how to use these two higher order skills, often appear such as establish multiple VO is used to accept the Create, Update scenarios, it is easy to be despised by veteran was laughed at, I hope you have a good grasp of it.
Finally, I am Miao Jam, an architect writing code and programmer working on architecture. I look forward to your forwarding and attention. Of course, you can also add my personal wechat jianzh5, let’s talk about technology together!
Github address: github.com/jianzh5/clo…