preface

Parameter verification is the first line of defense for back-end requests. The more unqualified requests are intercepted, the less resources they consume.

To verify the parameters, we might get something like this:

@RestController
@RequestMapping("/user")
public class UserController extends BaseController {

    @PostMapping("/add")
    public ApiResult addUser(@RequestBody User user) {
        if (user == null) {
            return ApiResult.fail("Object cannot be empty");
        }
        if (StringUtils.isEmpty(user.getAccount()) || StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getEmail())) {
            return ApiResult.fail("Account number, password, or email address cannot be empty");
        }
        if (user.getAccount().length() < 6 || user.getAccount().length() > 11) {
            return ApiResult.fail("Account must be 6-11 characters long");
        }
        if (user.getPassword().length() < 6 || user.getPassword().length() > 16) {
            return ApiResult.fail("Password must be 6-16 characters long");
        }
        if(! Pattern.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", user.getEmail())) {
            return ApiResult.fail("Email format is incorrect");
        }
        // Add a user
        returnApiResult.success(); }}Copy the code

There’s nothing wrong with this implementation, but it just doesn’t look elegant.

Let’s use the Spring Validator to optimize this code.

The specific implementation

Validator + BindResult

First, define the verification rule on the object by means of annotations and specify the information after the verification failure, as follows:

@Getter
@Setter
public class User {

    @NotNull(message = "User ID cannot be empty")
    private Long id;

    @NotNull(message = "User account cannot be empty.")
    @Size(min = 6, max = 11, message = "Account must be 6-11 characters long")
    private String account;

    @NotNull(message = "User password cannot be empty")
    @Size(min = 6, max = 11, message = "Password must be 6-16 characters long")
    private String password;

    @NotNull(message = "User's mailbox cannot be empty.")
    @Email(message = "Email format is incorrect")
    private String email;
}
Copy the code

After the verification rule is defined, add @vaild annotation and BindResult parameter to the interface to complete the verification, as follows:

@RestController
@RequestMapping("/user")
public class UserController extends BaseController {

    @PostMapping("/add")
    public ApiResult addUser(@RequestBody @Valid User user, BindingResult bindingResult) {
        // Error message is encapsulated in BindingResult when parameter verification fails
        for (ObjectError error : bindingResult.getAllErrors()) {
            return ApiResult.fail(error.getDefaultMessage());
        }
        // Add a user
        returnApiResult.success(); }}Copy the code

If we do not fill in the user account when accessing the interface, the following result is returned:

{
  "code": 500."data": null."message": "User account cannot be empty."
}
Copy the code

This is a very convenient way to verify parameters. However, it is not difficult to see that when multiple interfaces need parameter validation, you need to add the parameter BindingResult to each interface. Repeat the following:

// Error message is encapsulated in BindingResult when parameter verification fails
for (ObjectError error : bindingResult.getAllErrors()) {
    return ApiResult.fail(error.getDefaultMessage());
}
Copy the code

Programmers don’t just know Ctrl+C and Ctrl+V, we can solve this problem with unified exception handling.

Validator + Unified handling of exceptions

If not add parameters in the interface BindingResult, check failure will throw MethodArgumentNotValidException abnormalities, and checking in the abnormal failure information.

So as long as the unified MethodArgumentNotValidException exception handling.

In the article “Three ways to Handle Exceptions uniformly”, there are three ways to handle exceptions uniformly. The @ControllerAdvice + @ExceptionHandler method is used here

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    / * * * MethodArgumentNotValidException exception handling * /
    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        for (ObjectError error : bindingResult.getAllErrors()) {
            // Return check information
            return ApiResult.fail(error.getDefaultMessage());
        }
        return ApiResult.fail("Service is abnormal. Please try again later."); }}Copy the code

After unified handling of exceptions, the @vaild annotation can be added to the interface, which is quite concise.

@RestController
@RequestMapping("/user")
public class UserController extends BaseController {

    @PostMapping("/add")
    public ApiResult addUser(@RequestBody @Valid User user) {
        // Add a user
        returnApiResult.success(); }}Copy the code

At this point, parameter verification can be used elegantly.

But don’t think it’s all over. While the annotations provided by default will validate most cases, there are some special cases, such as validating a user’s phone number, for which there is no readily available Validator.

In this case, you need to customize the Validator.

The custom Validator

You need to customize annotations and implement validation logic.

Here we define an annotation @phone to verify the Phone number format:

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {PhoneValidator.class})
public @interface Phone {

    String message(a) default"Mobile phone number format is incorrect"; Class<? >[] groups()default {};

    Class<? extends Payload>[] payload() default {};
}
Copy the code

Use @constraint (validatedBy = {phonevalidator.class}) to specify that the validator class is PhoneValidator.

public class PhoneValidator implements ConstraintValidator<Phone.String> {

    @Override
    public boolean isValid(String phone, ConstraintValidatorContext context) {
        // Verify mobile phone format
        return Pattern.matches("^1[3-9]\\d{9}", phone); }}Copy the code

This allows you to verify the Phone number format with @phone.

public class User {

    @NotNull(message = "User's phone number cannot be empty.")
    @Phone(message = "Mobile phone number format is incorrect")
    private String phone;
}
Copy the code

conclusion

Parameter verification is implemented elegantly through unified processing of Validator and exception. With a custom Validator, you can implement a variety of complex validators.

Thanks for reading, and if it helps, just click a like!

The source code

Github.com/zhuqianchan…

Review past

  • Build backend frameworks from scratch – keep updating