Still using if else for parameter verification? Come and learn advanced parameter verification

I. Introduction

In the last article, Springboot implements elegant parameter validation (Spring Validation) and says goodbye to if else . We introduced the primary usage of Spring Validation. In actual development, whether it is the constraints defined by Bean Validation or the additional constraints of Hibernate Validator , are unable to meet our complex business scenarios. So, we need custom constraints. There are only two steps to develop custom constraints:

  1. Write annotations for custom constraints;
  2. Write a custom validator ConstraintValidator.

Next, let us implement a custom constraint together, which is used to verify that the parameters must be within the range of enumeration values.

2. Custom verification

For example, in a very common user registration scenario, the gender of the user must generally be limited. Here we do not consider the case where the same sex is unknown, but only consider the case of male and female. 0: represents female, 1: represents male,

2.1 Define the GenderArrayValuable interface

In order to make it easier for us to obtain enumeration type values ​​later, I first define an interface GenderArrayValuable , which defines a method that returns an int[] data. We will implement this interface in GenderEnum later .

package com.ratel.validation.core.validator;

/**
 * 可生成 Int 数组的接口
 */
public interface GenderArrayValuable {
    
    

    /**
     * @return int 数组
     */
    int[] array();

}

2.2 Define gender GenderEnum enumeration class

The GenderEnum enumeration class implements the IntArrayValuable interface of GenderArrayValuable and returns the value array ARRAYS.

import com.ratel.validation.core.validator.GenderArrayValuable;

import java.util.Arrays;

public enum GenderEnum implements GenderArrayValuable {
    
    

    MALE(1, "男"),
    FEMALE(0, "女");

    /**
     * 值数组
     */
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(GenderEnum::getValue).toArray();

    /**
     * 性别值
     */
    private final Integer value;
    /**
     * 性别名
     */
    private final String name;

    GenderEnum(Integer value, String name) {
    
    
        this.value = value;
        this.name = name;
    }

    public Integer getValue() {
    
    
        return value;
    }

    public String getName() {
    
    
        return name;
    }

    @Override
    public int[] array() {
    
    
        return ARRAYS;
    }

}

2.3 Custom @GenderCheck custom constraint annotation

package com.ratel.validation.core.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;

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

    /**
     * @return 实现 IntArrayValuable 接口的
     */
    Class<? extends GenderArrayValuable> value();

    /**
     * @return 提示内容
     */
    String message() default "必须在指定范围 {value}";

    /**
     * @return 分组
     */
    Class<?>[] groups() default {
    
    };

    /**
     * @return Payload 数组
     */
    Class<? extends Payload>[] payload() default {
    
    };

    /**
     *  Defines several {@code @GenderCheck} constraints on the same element.
     */
    @Target({
    
    METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
    
    

       GenderCheck[] value();

    }

}

2.4 Validator GenderValidator for custom constraints

package com.ratel.validation.core.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;

public class GenderValidator implements ConstraintValidator<GenderCheck, Integer> {
    
    

    /**
     * 值数组
     */
    private Set<Integer> values;

    @Override
    public void initialize(GenderCheck annotation) {
    
    
        GenderArrayValuable[] values = annotation.value().getEnumConstants();
        if (values.length == 0) {
    
    
            this.values = Collections.emptySet();
        } else {
    
    
            this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toSet());
        }
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
    
    
        // 1 校验通过
        if (values.contains(value)) {
    
    
            return true;
        }
        // 2 校验不通过,自定义提示语句(因为,注解上的 value 是枚举类,无法获得枚举类的实际值)
        context.disableDefaultConstraintViolation(); // 3 禁用默认的 message 的值
        context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
                .replaceAll("\\{value}", values.toString())).addConstraintViolation(); // 4. 重新添加错误提示语句
        return false; // 5 
    }

}

Implement the ConstraintValidator interface.

  • The first generic type is A extends Annotation , which sets the annotations of the corresponding custom constraints. For example, here we set the @GenderEnum annotation.
  • The second generic type is T, which sets the type of the corresponding parameter value. For example, here we set the Integer type.

Implement initialize(A constraintAnnotation)the method , get the values() attribute of the @GenderEnum annotation , get the value array, and set it to the values ​​attribute.

Implement boolean isValid(T var1, ConstraintValidatorContext var2); the method to check whether the parameter value is within the range of values.
At Note 1, verify that the parameter value is within the range, return true directly, and pass the verification.
At Note 2, the verification fails, and the prompt statement is customized.
At Note 5, the verification fails, so false is returned.
So far, we have completed the implementation of custom constraints.

2.5 Define UserUpdateGenderDTO

Define a UserUpdateGenderDTO entity class, add custom @GenderCheck(value = GenderEnum.class, message = "性别必须是 {value}")annotations , and limit the incoming parameter values, which must be within the GenderEnum enumeration range.


package com.ratel.validation.entity;



import com.ratel.validation.core.validator.GenderCheck;
import com.ratel.validation.enums.GenderEnum;

import javax.validation.constraints.NotNull;

/**
 * 用户更新性别 DTO
 */
public class UserUpdateGenderDTO {
    
    

    /**
     * 用户编号
     */
    @NotNull(message = "用户编号不能为空")
    private Integer id;

    /**
     * 性别
     */
    @NotNull(message = "性别不能为空")
    @GenderCheck(value = GenderEnum.class, message = "性别必须是 {value}")
    private Integer gender;

    public Integer getId() {
    
    
        return id;
    }

    public UserUpdateGenderDTO setId(Integer id) {
    
    
        this.id = id;
        return this;
    }

    public Integer getGender() {
    
    
        return gender;
    }

    public UserUpdateGenderDTO setGender(Integer gender) {
    
    
        this.gender = gender;
        return this;
    }

    @Override
    public String toString() {
    
    
        return "UserUpdateGenderDTO{" +
                "id=" + id +
                ", gender=" + gender +
                '}';
    }

}

2.6 Define an external access interface

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Min;

@RestController
@RequestMapping("/users")
@Validated
@Api(tags = "用户")
public class UserController {
    
    

    private Logger logger = LoggerFactory.getLogger(getClass());
        @PostMapping("/update_gender")
    public String updateGender(@Valid UserUpdateGenderDTO updateGenderDTO) {
    
    
        logger.info("[updateGender][updateGenderDTO: {}]", updateGenderDTO);
        return "性别更新成功";
    }
} 

2.7 Request interface for verification

We use the knife4j interface document here. When we pass the value of gender to a value other than [0, 1], an error message will be returned directly."请求参数不合法:性别必须是 [0, 1]",
insert image description here

3. Summary

I hope that after reading this article, all c friends can complete various places that require parameter verification more comfortably and elegantly. Not to mention, hurry up and complete the parameter verification for your own system, hehe.

Of course, one thing to note is that Bean Validation is more about stateless parameter validation. How do you understand it?

For example, the size and length of the parameter, judging whether the parameter is empty, whether it is an ID number, whether it is a mailbox, whether it is a mobile phone number, etc., which do not depend on external data sources, etc., are suitable for completion Spring Validationthrough
For example, verifying the user name, email address, unique mobile phone number, etc., relying on external data sources is not suitable for completion Spring Validationin .

Guess you like

Origin blog.csdn.net/weter_drop/article/details/130131581