需求描述

通过自定义注解,拓展 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 {

/**
* 字段名称
*
* @return String[]
*/
String[] value();

/**
* 提示信息
*
* @return String
*/
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 {

/**
* 字段名称
*
* @return String[]
*/
String[] value();

/**
* 提示信息
*
* @return String
*/
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 后非空;

评论