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
@Email 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

  1. Call the test1 method, promptingorg.springframework.web.bind.MethodArgumentNotValidExceptionabnormal
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
  1. Call the test2 method, promptingorg.springframework.validation.BindExceptionabnormal
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
  1. Call the test3 method, promptingjavax.validation.ConstraintViolationExceptionabnormal
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…