需求描述
通过自定义注解,拓展 JSR 303
规范,以实现校验参数中多个字段不可同时为空的情况。
即指定字段列表中,至少一个为非空值。
等价于多个字段 @NotNull、@NotEmpty、@NotAllBlank 的或操作。
解决方案
NotAllNull
注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}) @Retention(RUNTIME) @Constraint(validatedBy = NotAllNullConstraintValidator.class) @Documented @Repeatable(NotAllNull.List.class) public @interface NotAllNull {
String[] value();
String message() default "{javax.validation.constraints.NotAllNull.message}";
/** * 分组 * * @return Class<?>[] */ Class<?>[] groups() default {};
/** * 负载 * * @return Class<? extends Payload>[] */ Class<? extends Payload>[] payload() default {};
/** * 在相同元素上面定义多个 NotAllNull 注解 * * @see NotAllNull */ @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}) @Retention(RUNTIME) @Documented @interface List { NotAllNull[] value(); } }
|
校验器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Slf4j public class NotAllNullConstraintValidator implements ConstraintValidator<NotAllNull, Object> {
private static final String[] EMPTY = new String[0];
private String[] value = EMPTY;
@Override public void initialize(NotAllNull constraintAnnotation) { value = constraintAnnotation.value(); }
@Override public boolean isValid(Object object, ConstraintValidatorContext context) { if (object == null) { return false; } if (value.length == 1) { log.warn("NotAllNull 仅作用于一个字段 {}, 应当使用 @NotNull 注解!", value[0]); } return Arrays.stream(value) .map(it -> ReflectionUtils.findField(object.getClass(), it)) .filter(Objects::nonNull) .map(this::makeAccessible) .map(it -> ReflectionUtils.getField(it, object)) .anyMatch(Objects::nonNull); }
private Field makeAccessible(Field field) { ReflectionUtils.makeAccessible(field); return field; } }
|
NotAllEmpty
注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}) @Retention(RUNTIME) @Constraint(validatedBy = NotAllEmptyConstraintValidator.class) @Documented @Repeatable(NotAllEmpty.List.class) public @interface NotAllEmpty {
String[] value();
String message() default "{javax.validation.constraints.NotAllNull.message}";
/** * 分组 * * @return Class<?>[] */ Class<?>[] groups() default {};
/** * 负载 * * @return Class<? extends Payload>[] */ Class<? extends Payload>[] payload() default {};
/** * 在相同元素上面定义多个 NotAllNull 注解 * * @see NotAllNull */ @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}) @Retention(RUNTIME) @Documented @interface List { NotAllEmpty[] value(); } }
|
校验器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| @Slf4j public class NotAllEmptyConstraintValidator implements ConstraintValidator<NotAllEmpty, Object> {
private static final String[] EMPTY = new String[0];
private String[] value = EMPTY;
@Override public void initialize(NotAllEmpty constraintAnnotation) { value = constraintAnnotation.value(); }
@Override public boolean isValid(Object object, ConstraintValidatorContext context) { if (object == null) { return false; } if (value.length == 1) { log.warn("NotAllEmpty 仅作用于一个字段 {}, 应当使用 @NotEmpty 注解!", value[0]); } return Arrays.stream(value) .map(it -> ReflectionUtils.findField(object.getClass(), it)) .filter(Objects::nonNull) .map(this::makeAccessible) .map(it -> ReflectionUtils.getField(it, object)) .filter(Objects::nonNull) .anyMatch(this::isNotEmpty); }
private Field makeAccessible(Field field) { ReflectionUtils.makeAccessible(field); return field; }
private boolean isNotEmpty(Object value) { if (value instanceof String) { return StringUtils.isNotEmpty((String) value); } if (value.getClass().isArray()) { return ((Object[]) value).length > 0; } if (value instanceof Collection) { return !((Collection<?>) value).isEmpty(); } return false; } }
|
国际化消息提示
1 2
| javax.validation.constraints.NotAllNull.message=使用 @NotAllNull 的字段不能全为 null javax.validation.constraints.NotAllEmpty.message=使用 @NotAllEmpty 的字段不能全为空
|
其他
@NotAllBlank
(略),至于区别:
- NotNull:作用于所有类型,不是 Null 值;
- NotEmpty:作用于字符串、数组、集合,不是 Null 值,且非空;
- NotBlank:只作用于字符串,不是 Null 值,且 trim 后非空;