springboot中参数校验(validation)注解自

在进行参数验证的时候,往往存在现有的约束注解不能满足的情况,此时就需要我们自己定义validation注解了,下面来介绍一下如何自己定义一个验证注解。

有关参数校验的使用可详见《springboot中参数校验(validation)使用》

如何自定义一个validation注解?

自己定义validation注解需要使用@Constraint。@Constraint注解中是这样进行描述的:

 * Marks an annotation as being a Bean Validation constraint.
 * <p/>
 * A given constraint annotation must be annotated by a {@code @Constraint}
 * annotation which refers to its list of constraint validation implementations.
 * <p/>
 * Each constraint annotation must host the following attributes:
 * <ul>
 *     <li>{@code String message() default [...];} which should default to an error
 *     message key made of the fully-qualified class name of the constraint followed by
 *     {@code .message}. For example {@code "{com.acme.constraints.NotSafe.message}"}</li>
 *     <li>{@code Class<?>[] groups() default {};} for user to customize the targeted
 *     groups</li>
 *     <li>{@code Class<? extends Payload>[] payload() default {};} for
 *     extensibility purposes</li>
 * </ul>
 * <p/>
复制代码

可以看出,@Constraint用其来标注我们的注解是一个验证注解(Validation constraint),同时还指明了我们在自己定义验证注解的时候不可缺少的三个注解元素:

String message() default [...];
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
复制代码

自定义validation注解的时候,不仅要写出一个满足我们要求的注解,好需要搭配一个验证实现(validator),实现如下接口:

javax.validation.ConstraintValidator
复制代码

自定义validation注解案例

枚举验证注解@Enum

validation-api的注解和hibernate的扩展中没有对枚举值验证的注解,而在有些时候枚举值验证又是需要的,下面我们来自己定义这样一个validation注解,代码如下:

package com.lazycece.sbac.validation.constraint;

import com.lazycece.sbac.validation.constraint.validator.EnumValidator;

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

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @author lazycece
 * @date 2019/2/15
 */
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(Enum.List.class)
@Constraint(validatedBy = {EnumValidator.class})
public @interface Enum {

    String message() default "{*.validation.constraint.Enum.message}";

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

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

    /**
     * the enum's class-type
     *
     * @return Class
     */
    Class<?> clazz();

    /**
     * the method's name ,which used to validate the enum's value
     *
     * @return method's name
     */
    String method() default "ordinal";

    /**
     * Defines several {@link Enum} annotations on the same element.
     *
     * @see Enum
     */
    @Documented
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @interface List {
        Enum[] value();
    }
}
复制代码

枚举验证注解validator

注解验证逻辑的validator代码如下,通过反射方式获取枚举的值进行判断。

package com.lazycece.sbac.validation.constraint.validator;

import com.lazycece.sbac.validation.constraint.Enum;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Method;

/**
 * @author lazycece
 * @date 2019/2/15
 */
public class EnumValidator implements ConstraintValidator<Enum, Object> {

    private Enum annotation;

    @Override
    public void initialize(Enum constraintAnnotation) {
        this.annotation = constraintAnnotation;
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null) {
            return false;
        }

        Object[] objects = annotation.clazz().getEnumConstants();
        try {
            Method method = annotation.clazz().getMethod(annotation.method());
            for (Object o : objects) {
                if (value.equals(method.invoke(o))) {
                    return true;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return false;
    }
}
复制代码

使用方式

@Enum注解的使用需要给出枚举类和获取需要验证的枚举值的方法(默认情况下是ordinal)。

如果我们定义的枚举类如下所示,那么在验证的时候会设计到四种方式的验证:

package com.lazycece.sbac.validation.enums;

/**
 * @author lazycece
 * @date 2019/02/15
 */
public enum Role {

    ADMIN(1, "ADMIN"),
    TEST(2, "TEST"),
    DEVELOP(3, "DEVELOP");

    private int value;
    private String desc;

    Role(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}
复制代码
  • 参数param为枚举的ordinal值
    @Enum(clazz = Role.class, message = "role参数错误")
    复制代码
  • 参数param为枚举的name值
    @Enum(clazz = Role.class, method = "name", message = "role参数错误")
    复制代码
  • 参数param为枚举的value值
    @Enum(clazz = Role.class, method = "getValue", message = "role参数错误")
    复制代码
  • 参数param为枚举的desc值
    @Enum(clazz = Role.class, method = "getDesc", message = "role参数错误")
    复制代码

案例源码

案例源码地址:github.com/lazycece/sp…