Hello everyone, I am Misty.

A few days ago I wrote an article "How does SpringBoot unify the back-end return format?" Old birds all play like this! 》The reading effect is not bad, and it has been reprinted by many publishers. Today we continue to the second article to talk about how to integrate the parameter verification Validator in SprinBoot, and the high-level skills of parameter verification (custom verification, grouping check).

This article relies on the code foundation of the previous article, and a global exception checker has been added to the project. (The code warehouse is at the end of the article)

First of all, let's take a look at what is the Validator parameter validator, why do we need parameter validation?

Why need parameter verification

In the daily interface development, in order to prevent illegal parameters from affecting the business, it is often necessary to verify the interface parameters. For example, when logging in, you need to verify whether the username and password are empty. When creating a user, you need to verify the email and mobile phone. Whether the number format is accurate. Relying on the code to check the interface parameters one by one is too cumbersome, and the code readability is extremely poor.

The Validator framework is designed to solve the problem that developers write less code during development and improve development efficiency; Validator is specifically used for interface parameter verification, such as common required verification and email format verification. The user name must be between 6 and 12. Wait...

The Validator validation framework follows the JSR-303 validation specification (parameter validation specification), JSR is the abbreviation Java Specification Requests

Next, let's take a look at how to integrate the parameter verification 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>
Note: Starting from springboot-2.3 , the verification package is independent of a starter component, so validation and web need to be introduced, while the springboot-2.3 only needs to introduce web dependency.

The second step is to define the entity class for parameter verification

@Data
public class ValidVO {
    private String id;

    @Length(min = 6,max = 12,message = "appId长度必须位于6到12之间")
    private String appId;

    @NotBlank(message = "名字为必填项")
    private String name;

    @Email(message = "请填写正确的邮箱地址")
    private String email;

    private String sex;

    @NotEmpty(message = "级别不能为空")
    private String level;
}

In actual development, you need to set the corresponding business prompts, that is, the message attribute, for the fields that need to be verified.

Common constraint annotations are as follows:

annotationFunction
@AssertFalseCan be null, if not null, it must be false
@AssertTrueCan be null, if not null, it must be true
@DecimalMaxThe setting cannot exceed the maximum value
@DecimalMinThe setting cannot exceed the minimum value
@DigitsThe setting must be a number and the number of digits of the integer and the number of decimals must be within the specified range
@FutureThe date must be in the future of the current date
@PastThe date must be in the past of the current date
@MaxMaximum must not exceed this maximum
@MinThe maximum must not be less than this minimum
@NotNullCannot be null, can be empty
@NullMust be null
@PatternMust satisfy the specified regular expression
@SizeThe size() value of collection, array, map, etc. must be within the specified range
@EmailMust be in email format
@LengthThe length must be within the specified range
@NotBlankThe string cannot be null, nor can it be equal to "" after the string trim()
@NotEmptyCannot be null, size() of collection, array, map, etc. cannot be 0; after the string trim(), it can be equal to ""
@RangeValue must be within the specified range
@URLMust be a URL

Note: This table is only a simple description of the annotation function, and does not explain the attributes of each annotation; you can see the source code for details.

The third step is to define the verification class for testing

@RestController
@Slf4j
@Validated
public class ValidController {

    @ApiOperation("RequestBody校验")
    @PostMapping("/valid/test1")   
    public String test1(@Validated @RequestBody ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test1 valid success";
    }

    @ApiOperation("Form校验")
    @PostMapping(value = "/valid/test2")
    public String test2(@Validated ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test2 valid success";
    }
  
      @ApiOperation("单参数校验")
    @PostMapping(value = "/valid/test3")
    public String test3(@Email String email){
        log.info("email is {}", email);
        return "email valid success";
    }
}

Here we first define three methods test1, test2, test3, test1 using @RequestBody annotations to receive json data sent by the front end, test2 simulates form submission, and test3 simulates single parameter submission. Note that when using single parameter verification, you need to add @Validated annotations to the Controller, otherwise will not take effect.

The fourth step is to experience the effect

  1. Calling the test1 method, the prompt is org.springframework.web.bind.MethodArgumentNotValidException abnormal
POST http://localhost:8080/valid/test1
Content-Type: application/json

{
  "id": 1,
  "level": "12",
  "email": "47693899",
  "appId": "ab1c"
}
{
  "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.Pattern$Flag;@26139123,.*]; default message [不是一个合法的电子邮件地址]]...",
  "data": null,
  "timestamp": 1628239624332
}
  1. Calling the test2 method, the exception org.springframework.validation.BindException
POST http://localhost:8080/valid/test2
Content-Type: application/x-www-form-urlencoded

id=1&level=12&email=476938977&appId=ab1c
{
  "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 [名字为必填项]...",
  "data": null,
  "timestamp": 1628239301951
}
  1. Calling the test3 method, the exception javax.validation.ConstraintViolationException
POST http://localhost:8080/valid/test3
Content-Type: application/x-www-form-urlencoded

email=476938977
{
  "status": 500,
  "message": "test3.email: 不是一个合法的电子邮件地址",
  "data": null,
  "timestamp": 1628239281022
}

By adding Validator verification framework can help us to automatically achieve parameter verification.

Parameter exceptions are added to the global exception handler

Although we previously defined a global exception interceptor and saw that the interceptor is indeed effective, Validator verification framework is too bloated and not easy to read. In order to facilitate the front-end prompt, we need to simplify it.

Directly modify the previously defined RestExceptionHandler to intercept the three exceptions of parameter verification separately: javax.validation.ConstraintViolationException , org.springframework.validation.BindException , org.springframework.web.bind.MethodArgumentNotValidException , the 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);
}

Experience effect

POST http://localhost:8080/valid/test1
Content-Type: application/json

{
  "id": 1,
  "level": "12",
  "email": "47693899",
  "appId": "ab1c"
}
{
  "status": 400,
  "message": "名字为必填项; 不是一个合法的电子邮件地址; appId长度必须位于6到12之间",
  "data": null,
  "timestamp": 1628435116680
}

Does it feel refreshed?

Custom parameter verification

Although the annotations provided by Spring Validation are basically sufficient, in the face of complex definitions, we still need to define the relevant solutions ourselves to achieve automatic verification.

For example, the sex attribute in the above entity class only allows the front end to pass the enumeration values M and F. How to implement it?

The first step is to create a custom annotation

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumString.List.class)
@Documented
@Constraint(validatedBy = EnumStringValidator.class)//标明由哪个类执行校验逻辑
public @interface EnumString {
    String message() 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
    @interface List {

        EnumString[] value();
    }
}

The second step is to 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;
        }
        return enumStringList.contains(value);
    }
}

The third step is to add comments to the field

@ApiModelProperty(value = "性别")
@EnumString(value = {"F","M"}, message="性别只允许为F或M")
private String sex;

The fourth step is to 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
{
  "status": 400,
  "message": "性别只允许为F或M",
  "data": null,
  "timestamp": 1628435243723
}

Packet check

Certain fields of a VO object are required when it is added, but not required when it is updated. As above ValidVO the id and appId properties when new operations are optional , and in the editing operation are required , when adding a new name for the operation required , the face of this How would you deal with the scene?

In the actual development, I saw many students create two VO objects, ValidCreateVO , ValidEditVO to handle this kind of scene. This can indeed achieve the effect, but it will cause class expansion, and it is extremely easy to be ridiculed by the development veterans.

image.png

In fact, the Validator verification framework has considered this scenario and provided a solution, which is packet verification , but many students do not know it. To use packet verification, only three steps are required:

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{

        }
    }
}

Here we define a grouping interface ValidGroup to inherit javax.validation.groups.Default , and then define a number of different operation types in the grouping interface, Create, Update, Query, Delete. As for why we need to inherit Default, we will talk about it later.

The second step is to assign groups to the parameters in the model

@Data
@ApiModel(value = "参数校验类")
public class ValidVO {
    @ApiModelProperty("ID")
    @Null(groups = ValidGroup.Crud.Create.class)
    @NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
    private String id;

    @Null(groups = ValidGroup.Crud.Create.class)
    @NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
    @ApiModelProperty(value = "应用ID",example = "cloud")
    private String appId;

    @ApiModelProperty(value = "名字")
    @NotBlank(groups = ValidGroup.Crud.Create.class,message = "名字为必填项")
    private String name;
  
      @ApiModelProperty(value = "邮箱")
    @Email(message = "请填写正取的邮箱地址")
    privte String email;

       ...

}

Specify a group for the parameter, and use the default group for those that do not specify a group.

The third step is to specify a group for the method that requires parameter verification

@RestController
@Api("参数校验")
@Slf4j
@Validated
public class ValidController {

    @ApiOperation("新增")
    @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("更新")
    @PostMapping(value = "/valid/update")
    public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test4 valid success";
    }
}

Here we use the value attribute to assign the Create and Update groups add() and update()

The fourth step is to 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

We did not pass the id and appId parameters during Create, and the verification passed.

When we call the update method with the same parameters, it prompts a parameter verification error.

{
  "status": 400,
  "message": "ID不能为空; 应用ID不能为空",
  "data": null,
  "timestamp": 1628492514313
}

Since email belongs to the default group, and our group interface ValidGroup has inherited the Default , so it is also possible to check the parameters of the email field. like:

POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded

name=javadaily&level=12&email=476938977&sex=F
{
  "status": 400,
  "message": "请填写正取的邮箱地址",
  "data": null,
  "timestamp": 1628492637305
}

Of course, if your ValidGroup does not inherit the Default group, you need to add @Validated(value = {ValidGroup.Crud.Create.class, Default.class} to the code attribute to make the verification of the email

summary

Parameter verification is used very frequently in actual development, but many students still just stay in simple use, such as group verification, custom parameter verification, these two high-level skills are basically not used, such as establishment Multiple VOs are used to accept the Create and Update scenarios. It is easy to be despised and ridiculed by veterans. I hope you can master it.

The source code of the old bird series has been uploaded to GitHub, and the required ones are obtained in the public number [JAVA Japanese Record] Reply to the keyword 0923

飘渺Jam
343 声望54 粉丝

欢迎关注