How SpringBoot elegantly implements parameter verification

Nawking part

When we design the interface, parameter verification is an essential part. Strict parameter verification can ensure the rigor of the data. So in the SpringBoot project, how do you verify the parameters?

First let’s describe the requirements

User class, including username, user avatar, email address, age, mobile phone number, date of birth, requirements:

1. Username cannot be empty and consists of alphanumeric underscores, no more than 16 characters.

2. The user avatar cannot be empty and is a network picture.

3. The mailbox cannot be empty and must be a legal email address.

4. Age cannot be empty and greater than 0.

5. The mobile phone number cannot be empty and must be legal.

6. The date of birth cannot be empty and less than the current date.

For the above requirements, is your verification method as follows?

@PostMapping("/user/save")
public CommonResult save(@RequestBody UserReq req){<!-- -->
    CommonResult r = CommonResult. success(null);
    String username = req. getUsername();
    String avatar = req. getAvatar();
    Integer age = req. getAge();
    LocalDate bothDate = req. getBothDate();
    String email = req. getEmail();

    String pattern = "[A-Za-z0-9_] + ";
    Pattern p = Pattern.compile(pattern);
    Matcher m = p. matcher(username);
    if (!StringUtils.hasLength(username)) {<!-- -->
        return CommonResult.error(400, "How can the user name be empty");
    } else if (m. matches()) {<!-- -->
        return CommonResult.error(400, "The username format is wrong!");
    } else if (username. length() > 16) {<!-- -->
        return CommonResult.error(400, "The length of the user name is not greater than 16 characters");
    }

    if (!StringUtils.hasLength(avatar)) {<!-- -->
        return CommonResult.error(400, "How can the user avatar be empty");
    } else if (!avatar.startsWith("http://") & amp; & amp; !avatar.startsWith("https://")) {<!-- -->
        return CommonResult.error(400, "The user avatar format is incorrect!");
    }
    ...
    // The rest of the field validation is similar
    r.setData(req);
    return r;
}

The above code can realize parameter verification in various scenarios, but only two fields have written 20 lines of code, not to mention the complicated parameter verification in the background management system, some friends may ask, since parameter verification is so troublesome, So can the interface not be verified, and this troublesome work can be handed over to the front end?

Friends who have this idea should give up their thoughts as soon as possible. It is definitely not possible. The front end can only verify the requests sent from the front end of our system. If a hacker bypasses the browser and uses http tools to access the background service, and there are some bad intentions , then the service is in danger

So today we will talk about how to implement parameter verification elegantly, and say goodbye to if…else

Come back to business

Spring has integrated Hibernate Validator, which is also very convenient for SpringBoot. Use the @Validated annotation to implement declarative validation

Let’s get acquainted first

Null and non-null checks

  • @NotBlank : It can only be used when the string is not null, and the length of the string after #trim() must be greater than 0.
  • @NotEmpty : The elements of the collection object are not 0, that is, the collection is not empty, and it can also be used for strings that are not null.
  • @NotNull : Cannot be null.
  • @Null : Must be null .

Numerical Check

  • @DecimalMax(value) : The annotated element must be a number whose value must be less than or equal to the specified maximum value.
  • @DecimalMin(value) : The annotated element must be a number whose value must be greater than or equal to the specified minimum value.
  • @Digits(integer, fraction) : The annotated element must be a number whose value must be within an acceptable range.
  • @Positive : Determine positive numbers.
  • @PositiveOrZero : Judge positive or 0.
  • @Max(value) : The value of the field can only be less than or equal to this value.
  • @Min(value) : The value of the field can only be greater than or equal to this value.
  • @Negative : Judge negative numbers.
  • @NegativeOrZero : Judge negative or 0.

Boolean value check

  • @AssertFalse : The annotated element must be true .
  • @AssertTrue : The annotated element must be false .

Length Check

  • @Size(max, min) : Check whether the size of the field is between min and max, which can be a string, an array, a collection, a Map, etc.

Date Check

  • @Future : The annotated element must be a future date.
  • @FutureOrPresent : Determine whether the date is a future or present date.
  • @Past : Checks that the field’s date is in the past.
  • @PastOrPresent : Determine whether the date is a past or present date.

Other checks

  • @Email : The annotated element must be an email address.
  • @Pattern(value) : The annotated element must conform to the specified regular expression.

Hibernate Validator Additional Constraint Annotations

Under the org.hibernate.validator.constraints package, a series of constraint ( constraint ) annotations are defined. as follows:

  • @Range(min=, max=) : The annotated element must be within the appropriate range.
  • @Length(min=, max=) : The size of the annotated string must be within the specified range.
  • @URL(protocol=,host=,port=,regexp=,flags=) : The annotated string must be a valid URL.
  • @SafeHtml : Determine whether the submitted HTML is safe. For example, it cannot contain javascript scripts, etc.

Let’s experience it

1. First import dependencies

Xiaobai remembers that in version 2.1.6, this dependency does not need to be imported, but in which version, the official separated this dependency, and 2.4.2 needs to be imported manually

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2. Add parameter validation annotations to DTO

@Data
public class UserReq {<!-- -->

    @NotBlank(message = "How can the user name be empty")
    @Pattern(regexp = "[A-Za-z0-9_] + ", message = "Username format error")
    @Size(max = 16, message = "Username cannot be greater than 16 characters")
    private String username;

    @NotBlank(message = "How can the user avatar be empty")
    @Pattern(regexp = "[a-zA-z] + :\/\/[^\\s]*", message = "User avatar format error")
    private String avatar;

    @NotBlank(message = "How can the mailbox be empty")
    @Email(message = "The email format is incorrect")
    private String email;

    @NotNull(message = "How can age be empty")
    @Min(value = 0, message = "age must be greater than 0")
    private Integer age;

    @NotBlank(message = "How can the phone number be empty")
    @Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}", message = "The phone number format is wrong")
    private String phone;

    @NotNull(message = "How can the date of birth be empty")
    @Past(message = "The date of birth cannot be greater than the current")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate bothDate;
}

3. Add @Validated annotation to interface parameters

@PostMapping("/user/save")
public CommonResult save(@RequestBody @Validated UserReq req){<!-- -->
    CommonResult r = CommonResult. success(null);
    r.setData(req);
    return r;
}

4. Add a global exception handler to handle parameter verification exceptions

@RestControllerAdvice
public class GlobalExceptionHandler {<!-- -->

    @ExceptionHandler(value = ConstraintViolationException. class)
    public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {<!-- -->
        // concatenation error
        StringBuilder detailMessage = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : ex. getConstraintViolations()) {<!-- -->
            if (detailMessage. length() > 0) {<!-- -->
                detailMessage.append(";");
            }
            detailMessage.append(constraintViolation.getMessage());
        }
        return CommonResult.error(400, detailMessage.toString());
    }

    @ExceptionHandler(value = BindException. class)
    public CommonResult bindExceptionHandler(HttpServletRequest req, BindException ex) {<!-- -->
        // concatenation error
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex. getAllErrors()) {<!-- -->
            if (detailMessage. length() > 0) {<!-- -->
                detailMessage.append(";");
            }
            detailMessage.append(objectError.getDefaultMessage());
        }
        return CommonResult.error(400, detailMessage.toString());
    }

    @ExceptionHandler(value = MethodArgumentNotValidException. class)
    public CommonResult MethodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException ex) {<!-- -->
        // concatenation error
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex. getBindingResult(). getAllErrors()) {<!-- -->
            if (detailMessage. length() > 0) {<!-- -->
                detailMessage.append(";");
            }
            detailMessage.append(objectError.getDefaultMessage());
        }
        return CommonResult.error(400, detailMessage.toString());
    }


    /**
     * Handle other Exception exceptions
     *
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception. class)
    public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {<!-- -->
        return CommonResult. error(500, e. getMessage());
    }
}

5. Verify wrong parameters

image-20230522112409593

6. Verify the correct parameters

image-20230522112445841

Conclusion

1. In most scenarios, we can use the @Validated annotation. If there is a nested validation scenario, use the @Valid annotation to add it to the member property.

2. To ensure the robustness of the interface, parameter verification is an indispensable link, which cannot be omitted, and should be extremely strict, so that malicious users must not be given an opportunity.

3. It’s not easy to make, let’s go with one click and three consecutive times. Your support will always be my biggest motivation!