Don't write full screen if/else for parameter verification. I was almost persuaded to retreat

Posted by Michiel on Tue, 08 Feb 2022 06:16:23 +0100

Author: Jin Cheng\
Source: Juejin im/post/5d3fbeb46fb9a06b317b3c48

It's painful to encounter a large number of parameters for verification, and the verification information when throwing exceptions or constantly returning exceptions in the business. The code is quite lengthy and full of if else verification code. Today we'll learn javax of spring Validation annotated parameter verification.

1. Why use validator

javax. A series of annotations of validation can help us complete parameter verification and avoid cumbersome serial verification

Otherwise, our code looks like this:

//  http://localhost:8080/api/user/save/serial

/**
 * Go serial check
 *
 * @param userVO
 * @return
 */
@PostMapping("/save/serial")
public Object save(@RequestBody UserVO userVO) {
    String mobile = userVO.getMobile();

    //Manual parameter by parameter verification ~ writing method
    if (StringUtils.isBlank(mobile)) {
        return RspDTO.paramFail("mobile:Mobile phone number cannot be empty");
    } else if (!Pattern.matches("^[1][3,4,5,6,7,8,9][0-9]{9}$", mobile)) {
        return RspDTO.paramFail("mobile:Wrong mobile phone number format");
    }

    //Throw custom exceptions and other ~ writing methods
    if (StringUtils.isBlank(userVO.getUsername())) {
        throw new BizException(Constant.PARAM_FAIL_CODE, "User name cannot be empty");
    }

    // For example, write a map to return
    if (StringUtils.isBlank(userVO.getSex())) {
        Map<String, Object> result = new HashMap<>(5);
        result.put("code", Constant.PARAM_FAIL_CODE);
        result.put("msg", "Gender cannot be empty");
        return result;
    }
    //......... All kinds of writing
    userService.save(userVO);
    return RspDTO.success();
}

When the boss saw this, he must say that it was written after 9102, and then he was persuaded to retreat

2. What is javax validation

JSR303 is a set of standards for JavaBean parameter verification. It defines many commonly used verification annotations. We can directly add these annotations to the properties of our JavaBean (in the era of annotation oriented programming), so that we can verify when verification is needed.

I won't introduce the basics of Spring Boot. I recommend this practical tutorial:

https://github.com/javastacks...

SpringBoot has been included in starter web. You can reference dependencies in other projects and adjust the version yourself:

<!--jsr 303-->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<!-- hibernate validator-->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.2.0.Final</version>
</dependency>

3. Notes

  • @NotNull: cannot be null, but can be empty('', '', '')
  • @NotEmpty: cannot be null, and the length must be greater than 0 ('', '')
  • @NotBlank: it can only work on strings and cannot be null. After calling trim(), the length must be greater than 0("test"), that is, there must be actual characters

Only most of the validation constraint annotations provided by hibernate validator are listed here. Please refer to the official documentation of Hibernate validator for other validation constraint annotations and customized validation constraint annotation definitions.

4. Actual combat drill

Without much to say, we directly follow the line of practice and also use the fast framework of SpringBoot.

For detailed code, see:

https://github.com/leaJone/mybot

4.1. @Validated declares the parameters to check

Here we make annotation declaration at the controller layer

/**
 * Walk parameter verification annotation
 *
 * @param userDTO
 * @return
 */
@PostMapping("/save/valid")
public RspDTO save(@RequestBody @Validated UserDTO userDTO) {
    userService.save(userDTO);
    return RspDTO.success();
}

4.2. Annotate the fields of parameters

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.Date;

/**
 * @author LiJing
 * @ClassName: UserDTO
 * @Description: User transfer object
 * @date 2019/7/30 13:55
 */
@Data
public class UserDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    /*** User ID*/
    @NotNull(message = "user id Cannot be empty")
    private Long userId;

    /** user name*/
    @NotBlank(message = "User name cannot be empty")
    @Length(max = 20, message = "The user name cannot exceed 20 characters")
    @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "User nickname limit: up to 20 characters, including text, letters and numbers")
    private String username;

    /** cell-phone number*/
    @NotBlank(message = "Mobile phone number cannot be empty")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "Wrong mobile phone number format")
    private String mobile;

    /**Gender*/
    private String sex;

    /** mailbox*/
    @NotBlank(message = "Contact mailbox cannot be empty")
    @Email(message = "Incorrect email format")
    private String email;

    /** password*/
    private String password;

    /*** Creation time */
    @Future(message = "Time must be in the future")
    private Date createTime;

}

4.3. Add verification exception to global verification

MethodArgumentNotValidException is an exception during binding parameter verification in springBoot, which needs to be handled in springBoot. Other exceptions need to be handled with ConstraintViolationException.

In order to be elegant, we unified parameter exceptions and business exceptions into a global exception, and wrapped the exceptions of the control layer into our custom exceptions.

In order to be elegant, we have also made a unified structure, which encapsulates the requested code, msg and data into the structure, increasing the reusability of the code.

import com.boot.lea.mybot.dto.RspDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;

/**
 * @author LiJing
 * @ClassName: GlobalExceptionHandler
 * @Description: Global exception handler
 * @date 2019/7/30 13:57
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private static int DUPLICATE_KEY_CODE = 1001;
    private static int PARAM_FAIL_CODE = 1002;
    private static int VALIDATION_CODE = 1003;

    /**
     * Handle custom exceptions
     */
    @ExceptionHandler(BizException.class)
    public RspDTO handleRRException(BizException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(e.getCode(), e.getMessage());
    }

    /**
     * Method parameter verification
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public RspDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(PARAM_FAIL_CODE, e.getBindingResult().getFieldError().getDefaultMessage());
    }

    /**
     * ValidationException
     */
    @ExceptionHandler(ValidationException.class)
    public RspDTO handleValidationException(ValidationException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(VALIDATION_CODE, e.getCause().getMessage());
    }

    /**
     * ConstraintViolationException
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public RspDTO handleConstraintViolationException(ConstraintViolationException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(PARAM_FAIL_CODE, e.getMessage());
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public RspDTO handlerNoFoundException(Exception e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(404, "The path does not exist. Please check whether the path is correct");
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public RspDTO handleDuplicateKeyException(DuplicateKeyException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(DUPLICATE_KEY_CODE, "Duplicate data, please check and submit");
    }

    @ExceptionHandler(Exception.class)
    public RspDTO handleException(Exception e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(500, "System busy,Please try again later");
    }
}

4.4. test

As follows: it does return exception information and corresponding code during parameter verification, which makes it convenient for us to deal with parameter verification no longer.

In validationmessages Properties is the verified message. It has a written default message and supports i18n. You can read the source code appreciation.

5. User defined Parameter annotation

5.1. For example, let's have a custom ID verification annotation

@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdentityCardNumberValidator.class)
public @interface IdentityCardNumber {

    String message() default "ID number is not legal.";

    Class<?>[] groups() default {};

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

This annotation acts on the Field field and takes effect at runtime. It triggers the authentication class IdentityCardNumber.

  • message customized prompt messages, mainly from validationmessages Properties, which can also be customized according to the actual situation
  • groups mainly classifies validators here. Different validator operations will be performed in different group s
  • payload is mainly for bean s and is rarely used.

5.2. Then customize the Validator

This is the logic code for real verification:

public class IdentityCardNumberValidator implements ConstraintValidator<IdentityCardNumber, Object> {

    @Override
    public void initialize(IdentityCardNumber identityCardNumber) {
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        return IdCardValidatorUtils.isValidate18Idcard(o.toString());
    }
}

IdCardValidatorUtils is in the project source code and can be viewed by yourself

5.3. Use custom annotations

@NotBlank(message = "The ID number can not be empty.")
@IdentityCardNumber(message = "Incorrect ID card information,Please check and submit")
private String clientCardNo;

5.4. Verification using groups

Some babies say that the same object needs to be reused. For example, UserDTO needs to check userId when updating, but it doesn't need to check userId when saving. In both cases, you need to check username, so use groups:

First define the grouping interfaces Create and Update of groups.

import javax.validation.groups.Default;

public interface Create extends Default {
}

import javax.validation.groups.Default;

public interface Update extends Default{
}

Then declare the verification group @ Validated where verification is needed

/**
 * groups combination verification with parameter verification annotation
 *
 * @param userDTO
 * @return
 */
@PostMapping("/update/groups")
public RspDTO update(@RequestBody @Validated(Update.class) UserDTO userDTO) {
    userService.updateById(userDTO);
    return RspDTO.success();
}

Define the grouping type of groups = {} on the field in DTO

@Data
public class UserDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    /*** User ID*/
    @NotNull(message = "user id Cannot be empty", groups = Update.class)
    private Long userId;

    /**
     * user name
     */
    @NotBlank(message = "User name cannot be empty")
    @Length(max = 20, message = "The user name cannot exceed 20 characters", groups = {Create.class, Update.class})
    @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "User nickname limit: up to 20 characters, including text, letters and numbers")
    private String username;

    /**
     * cell-phone number
     */
    @NotBlank(message = "Mobile phone number cannot be empty")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "Wrong mobile phone number format", groups = {Create.class, Update.class})
    private String mobile;

    /**
     * Gender
     */
    private String sex;

    /**
     * mailbox
     */
    @NotBlank(message = "Contact mailbox cannot be empty")
    @Email(message = "Incorrect email format")
    private String email;

    /**
     * password
     */
    private String password;

    /*** Creation time */
    @Future(message = "Time must be in the future", groups = {Create.class})
    private Date createTime;

}

Note: when declaring grouping, try to add extend javax validation. groups. Default otherwise, when you declare @ Validated(Update.class), the verification group @ Email(message = "incorrect email format") will appear when you do not add groups = {Default.class} by default. The verification will not be performed because the default verification group is groups = {Default.class}

5.5.restful style usage

When checking multiple parameters or in the form of @ RequestParam, @ Validated should be added to the controller.

@GetMapping("/get")
public RspDTO getUser(@RequestParam("userId") @NotNull(message = "user id Cannot be empty") Long userId) {
    User user = userService.selectById(userId);
    if (user == null) {
        return new RspDTO<User>().nonAbsent("user does not exist");
    }
    return new RspDTO<User>().success(user);
}
@RestController
@RequestMapping("user/")
@Validated
public class UserController extends AbstractController {

....San Lo code...

6. Summary

It's very simple to use. soEasy, the unified structure return and unified parameter verification that focus on participation, are the magic weapon to reduce a lot of try catch in our code. I think in the project, handling exceptions well and managing exceptions well in the log is a good sublimation. The article is simple, just an advanced note of a rookie

Here are just personal opinions and technical dishes. You are welcome to comment

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2022 latest version)

2.Hot! The Java collaboration is coming...

3.Spring Boot 2.x tutorial, too complete!

4.20w programmer red envelope cover, get it quickly...

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Topics: Java