在实际生产项目中为了提高代码的可阅读性,往往只需要注重业务点,而一些内容则交给框架去实现---->这里阐述的是对前端返回的参数进行校验
此时就要想到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这个注解的用法,直接将错误返回给前台,根本就不会进入我们的业务逻辑