在实际生产项目中为了提高代码的可阅读性,往往只需要注重业务点,而一些内容则交给框架去实现---->这里阐述的是对前端返回的参数进行校验

此时就要想到javax.validation包下的一系列注解,如下图

自定义注解之参数校验_构造器

 

 这里就不追溯各个注解的作用了,用过的人都知道

现在面临一个问题,假如上面的注解并不能实现我的业务参数校验呢?

比如说:我需要校验一个状态值,这个状态值只能是0或者1,如果不是就提示前台参数错误

上面的注解就不能满足我的业务需求了,此时需要自定义校验注解

现在我们来参考一个jar包中定义好的注解,来模仿一下

/**
 * The annotated element must not be {@code null}.
 * Accepts any type.
 *
 * @author Emmanuel Bernard
 */
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })  
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })   //可以理解成校验构造器,校验通过哪些构造器
public @interface NotNull {

    String message() default "{javax.validation.constraints.NotNull.message}";    //校验失败返回的默认提示语,这里的值应该写在配置文件中 ValidationMessages.properties

    Class<?>[] groups() default { };                   //校验分组信息

    Class<? extends Payload>[] payload() default { };      //加载的负载,这个不需要了解,框架里的东西,我也不怎么熟悉

    /**
     * Defines several {@link NotNull} annotations on the same element.
     *
     * @see javax.validation.constraints.NotNull
     */
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        NotNull[] value();
    }
}

由上面系统代码可知重要的有三点 

@Constraint(validatedBy = { })   校验注解的构造器
message()     默认校验失败返回信息
default()    分组信息

现在我们就来一个模仿

@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class })  //校验构造器
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {

    String message() default "{com.common.valid.ListValue.message}";

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

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

    int[] vals() default { };
}

先忽略@Constraint注解里面的值

 

我们来看下@Constraint的源代码

@Documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface Constraint {

	/**
	 * {@link ConstraintValidator} classes implementing the constraint. The given classes
	 * must reference distinct target types for a given {@link ValidationTarget}. If two
	 * {@code ConstraintValidator}s refer to the same type, an exception will occur.
	 * <p>
	 * At most one {@code ConstraintValidator} targeting the array of parameters of
	 * methods or constructors (aka cross-parameter) is accepted. If two or more
	 * are present, an exception will occur.
	 *
	 * @return array of {@code ConstraintValidator} classes implementing the constraint
	 */
	Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
}

  

此时我们看到者这个  Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
心里一阵疑问,这他妈又是什么,仔细看注释 发现这个一个参数校验的构造器,而且是一个数组,并且必须实现ConstraintValidator这个接口
此时再来看下ConstraintValidator这个接口
public interface ConstraintValidator<A extends Annotation, T> {

    /**
     * Initializes the validator in preparation for
     * {@link #isValid(Object, ConstraintValidatorContext)} calls.
     * The constraint annotation for a given constraint declaration
     * is passed.
     * <p>
     * This method is guaranteed to be called before any use of this instance for
     * validation.
     * <p>
     * The default implementation is a no-op.
     *
     * @param constraintAnnotation annotation instance for a given constraint declaration
     */
    default void initialize(A constraintAnnotation) {
    }

    /**
     * Implements the validation logic.
     * The state of {@code value} must not be altered.
     * <p>
     * This method can be accessed concurrently, thread-safety must be ensured
     * by the implementation.
     *
     * @param value object to validate
     * @param context context in which the constraint is evaluated
     *
     * @return {@code false} if {@code value} does not pass the constraint
     */
    boolean isValid(T value, ConstraintValidatorContext context);
}

这个接口只有两个方法,一个是初始化方法,一个是校验方法

此时我们再度模仿写个类,实现这个接口。这里尤为注意的是这个接口有两个泛型 

<A extends Annotation, T> 这里的A就是我们自己定义的校验注解, T就是需要校验的参数类型
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {

    private Set<Integer> set = new HashSet<>();

    //初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

    //判断校验是否成功
    /**
     * @param value 需要校验的值
     * @param context 校验的上下文环境
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}
此时再结合我上面的注解写的内容,也就一目了然

/**
 * 显示状态[0-不显示;1-显示]
 */
@ListValue(vals={0,1})
private Integer showStatus;

这里就是校验的showStatus这个字段,这个字段只能为int类型0或者1,不然就会报错

此时我们再用@ControllerAdvice这个注解的用法,直接将错误返回给前台,根本就不会进入我们的业务逻辑