A preface

Before the service logic is executed, the system verifies the input data to check whether the data is valid. Therefore, we may write a lot of judgment logic such as if and else, especially when the same data appears in different methods, the verification logic code will appear repeatedly, resulting in code redundancy, poor readability and maintainability.

The Spring framework provides a validator component that provides a simple and easy way to verify the integrity and validity of data.

Jsr-303 is Java’s standard framework for validation of Bean data. It defines a set of validation annotations that can be annotated on member variables, attribute methods, and so on.

Hibernate-validator provides an implementation of this standard. When developing web applications with Springboot, we introduce the spring-boot-starter-Web dependency. It introduces the spring-boot-starter-validation dependency by default, and spring-boot-starter-Validation references the Hibernate-Validator dependency.

However, in older versions of spring-boot-starter-Web, spring-boot-starter-validation is no longer referenced by default, and hibernate- Validator dependencies are not introduced by default, requiring manual dependency addition.

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.7. The Final</version>
</dependency>
Copy the code

Hibernate – Validator has many very simple validation annotations, such as NotNull, @notempty, @min, @max, @email, @positiveOrZero, and so on. These annotations solve most of our data validation problems. As follows:

package com.nobody.dto;

import lombok.Data;

import javax.validation.constraints.*;

@Data
public class UserDTO {

    @notblank (message = "name cannot be blank ")
    private String name;

    @min (value = 18, message = "age < 18")
    private int age;

	@notempty (message = "mailbox cannot be empty ")
    @email (message = "Email format is not correct ")
    private String email;
}
Copy the code

Custom parameter validator

However, these annotations in hibernate-Validator don’t necessarily meet all of our requirements, and the logic we want to verify is more complex than that. Therefore, we can customize our own parameter validators.

It is essential to introduce dependencies first.

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.7. The Final</version>
</dependency>
Copy the code

Is not the fund very hot recently, a large number of leeks madly into the tide of buying funds. I take the user to open an account as an example, first of all to check the user is not an adult (that is, not less than 18 years old), and the name is not with the “new leek” beginning, meet the conditions to allow to open an account.

Define an annotation to verify that the user’s name starts with “new leek”.

package com.nobody.annotation;

import com.nobody.validator.IsLeekValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/ * * *@DescriptionCheck whether the annotation of leek *@Author Mr.nobody
 * @Date 2021/3/11
 * @Version1.0 * /
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Constraint(validatedBy = IsLeekValidator.class) // Specify our custom validation class
public @interface IsLeek {

    /** * Specifies whether to forcibly verify **@returnBoolean value */ for whether validation is mandatory
    boolean required(a) default true;

    /** * An error message is reported when the verification fails **@returnError message */ is reported when the verification fails
    String message(a) default"This user is not a zero, can not open an account!";

    /** * Classifies validators. Different groups perform different validators **@returnThe category type of the Validator */Class<? >[] groups()default {};

    /** * is mainly for beans, and ** is rarely used@returnLoad * /
    Class<? extends Payload>[] payload() default {};

}
Copy the code

Define a validation class that implements the ConstraintValidator interface. The interface uses generics and needs to specify two parameters, the first is the custom annotation and the second is the data type to validate. The initialize method does some initialization, and its parameters are the annotations we use to get the runtime annotation information. The isValid method is the validation logic to implement, into which the annotated object is passed.

package com.nobody.validator;

import com.nobody.annotation.IsLeek;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/ * * *@DescriptionCustom validator *@Author Mr.nobody
 * @Date 2021/3/11
 * @Version1.0 * /
public class IsLeekValidator implements ConstraintValidator<IsLeek.String> {

    // Whether to enforce verification
    private boolean required;

    @Override
    public void initialize(IsLeek constraintAnnotation) {
        this.required = constraintAnnotation.required();
    }

    @Override
    public boolean isValid(String name, ConstraintValidatorContext constraintValidatorContext) {
        if (required) {
            // If the name starts with "new leek", the check is passed
            return! StringUtils.isEmpty(name) && name.startsWith("New Leeks");
        }
        return false; }}Copy the code

Use custom annotations

With the above steps, our custom validation annotation is complete and we use the test to test the effect.

package com.nobody.dto;

import com.nobody.annotation.IsLeek;
import lombok.Data;

import javax.validation.constraints.*;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2021/3/11
 * @Version1.0 * /
@Data
public class UserDTO {

    @notblank (message = "name cannot be blank ")
    @IsLeek // Our custom annotations
    private String name;

    @min (value = 18, message = "age < 18")
    private int age;

    @notempty (message = "mailbox cannot be empty ")
    @email (message = "Email format is not correct ")
    private String email;
}
Copy the code

Write an interface, simulate the user registration service, call the test. Note that validation is enabled with the @valid annotation, otherwise it will not take effect.

package com.nobody.controller;

import com.nobody.dto.UserDTO;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2021/3/11
 * @Version1.0 * /
@RestController
@RequestMapping("user")
public class UserController {

    @PostMapping("add")
    public UserDTO add(@RequestBody @Valid UserDTO userDTO) {
        System.out.println(">>> User accounts successfully...");
        returnuserDTO; }}Copy the code

If you don’t by parameter calibration, throws MethodArgumentNotValidException anomaly, and then returned to the interface under our global processing.

package com.nobody.exception;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import lombok.extern.slf4j.Slf4j;

/ * * *@DescriptionUnified exception handling *@Author Mr.nobody
 * @Date 2020/10/23
 * @Version1.0 * /
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // Handle interface parameter data format error
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    public Object errorHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        returne.getBindingResult().getAllErrors(); }}Copy the code

We first test the user name without “new leek” prefix test, found that the verification failed, proving that the annotation is effective.

POST http://localhost:8080/user/add
Content-Type: application/json

{"name": "Little green"."age": 19, "email": "[email protected]"}
Copy the code
[{"codes": [
      "IsLeek.userDTO.name"."IsLeek.name"."IsLeek.java.lang.String"."IsLeek"]."arguments": [{"codes": [
          "userDTO.name"."name"]."arguments": null,
        "defaultMessage": "name"."code": "name"
      },
      true]."defaultMessage": "This user is not a zero, can not open an account!"."objectName": "userDTO"."field": "name"."rejectedValue": "Little green"."bindingFailure": false."code": "IsLeek"
  }

Copy the code

If multiple parameters fail to be verified, error information can be obtained. As shown below, the name and mailbox failed to be verified.

POST http://localhost:8080/user/add
Content-Type: application/json

{"name": "Little green"."age": 19, "email": "84513654"}
Copy the code
[{"codes": [
      "Email.userDTO.email"."Email.email"."Email.java.lang.String"."Email"]."arguments": [{"codes": [
          "userDTO.email"."email"]."arguments": null,
        "defaultMessage": "email"."code": "email"
      },
      [],
      {
        "defaultMessage": ". *"."codes": [
          ". *"]."arguments": null
      }
    ],
    "defaultMessage": "Email format is incorrect"."objectName": "userDTO"."field": "email"."rejectedValue": "84513654"."bindingFailure": false."code": "Email"
  },
  {
    "codes": [
      "IsLeek.userDTO.name"."IsLeek.name"."IsLeek.java.lang.String"."IsLeek"]."arguments": [{"codes": [
          "userDTO.name"."name"]."arguments": null,
        "defaultMessage": "name"."code": "name"
      },
      true]."defaultMessage": "This user is not a zero, can not open an account!"."objectName": "userDTO"."field": "name"."rejectedValue": "Little green"."bindingFailure": false."code": "IsLeek"}]Copy the code

The following is the verification of all parameters:

POST http://localhost:8080/user/add
Content-Type: application/json

{"name": "New Leek little Green"."age": 19, "email": "[email protected]"}
Copy the code
{
  "name": "New Leek little Green"."age": 19,
  "email": "[email protected]"
}
Copy the code

We might use the UserDTO object to receive parameters in different interfaces, such as new and modified interfaces. The new interface does not need to verify the userId. Verify the userId when modifying the interface. The groups field in the annotation comes in handy. The combination of the groups and @ indicates whether annotations need to be Validated or not.

We first define two groups grouping interfaces Update and Create, and inherit the Default interface. You can also omit the Default interface, because groups = {default.class} defaults to groups = {default.class}. So, the Default interface is inherited, and the @ “age” (Create. Class) annotation is used to validate groups = {default.class}.

package com.nobody.annotation;

import javax.validation.groups.Default;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2021/3/13
 * @Version1.0 * /
public interface Create extends Default {}Copy the code
package com.nobody.annotation;

import javax.validation.groups.Default;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2021/3/13
 * @Version1.0 * /
public interface Update extends Default {}Copy the code

Fill in the value of groups where annotations are used.

package com.nobody.dto;

import com.nobody.annotation.Create;
import com.nobody.annotation.IsLeek;
import com.nobody.annotation.Update;
import lombok.Data;

import javax.validation.constraints.*;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2021/3/11
 * @Version1.0 * /
@Data
public class UserDTO {

    @notblank (message = "user ID cannot be blank ", groups = update.class)
    private String userId;

    @notblank (message = "name cannot be blank ", groups = {update.class, create.class})
    @IsLeek
    private String name;

    @min (value = 18, message = "age < 18")
    private int age;

    @notempty (message = "mailbox cannot be empty ")
    @email (message = "Email format is not correct ")
    private String email;
}
Copy the code

Finally, where verification needs to be declared, specify @Validated.

package com.nobody.controller;

import com.nobody.annotation.Create;
import com.nobody.annotation.Update;
import com.nobody.dto.UserDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2021/3/11
 * @Version1.0 * /
@RestController
@RequestMapping("user")
public class UserController {

    @PostMapping("add")
    public Object add(@RequestBody @Validated(Create.class) UserDTO userDTO) {
        System.out.println(">>> User accounts successfully...");
        return userDTO;
    }

    @PostMapping("update")
    public Object update(@RequestBody @Validated(Update.class) UserDTO userDTO) {
        System.out.println(">>> User information modified successfully...");
        returnuserDTO; }}Copy the code

When the ADD interface is called, it passes without passing the userId, that is, the userId is not verified.

POST http://localhost:8080/user/add
Content-Type: application/json

{"name": "New Leek little Green"."age": 18."email": "[email protected]"}
Copy the code
{
  "userId": null."name": "New Leek little Green"."age": 18."email": "[email protected]"
}
Copy the code

If the userId is not passed when the update interface is invoked, the verification fails.

POST http://localhost:8080/user/update
Content-Type: application/json

{"name": "New Leek little Green"."age": 18."email": "[email protected]"}
Copy the code
[{"codes": [
      "NotBlank.userDTO.userId"."NotBlank.userId"."NotBlank.java.lang.String"."NotBlank"]."arguments": [{"codes": [
          "userDTO.userId"."userId"]."arguments": null."defaultMessage": "userId"."code": "userId"}]."defaultMessage": "User ID cannot be empty"."objectName": "userDTO"."field": "userId"."rejectedValue": null."bindingFailure": false."code": "NotBlank"}]Copy the code

This demo project has been uploaded to Github, if you need to download, welcome Star. Github.com/LucioChn/sp…