背景
validation中提供的注解都是针对单个参数的,如果两个参数之间有关联关系就只能在代码里判断了,比如:
@Data
public class TestPo {
private String type;
//当type为定时发送时,必须填写发送时间,当type为立即发送时,可以不填发送时间
private Date sendTime;
//当type为草稿时,sendContent可以为空,否则必须有值
private SendContent sendContent;
}
这种就只能在代码中判断type的值然后决定另外两个参数的校验。
方法1 使用@ScriptAssert注解
@Data
@ScriptAssert.List(value = {
@ScriptAssert(script = "_this.type == '定时发送' && _this.sendTime != null",
lang = "javascript",
message = "当type为定时发送时,必须填写发送时间"),
@ScriptAssert(script = "_this.type != '草稿' && _this.sendContent != null",
lang = "javascript",
message = "当type不是草稿时,sendContent必填")
})
public class TestPo {
@NotBlank(message = "type不能为空")
private String type;
//当type为定时发送时,必须填写发送时间,当type为立即发送时,可以不填发送时间
private Date sendTime;
@Valid
//当type为草稿时,sendContent可以为空,否则必须有值
private SendContent sendContent;
}
这种方法需要使用javascript,对于部分人来说可能不够直观也很难调试。
方法2 自定义注解+spring表达式
针对这种情况我利用spring表达式写了一个自定义注解来解决这个问题。
第一步:自定义注解
/**
* 多属性关联校验注解
* 用于校验多个属性之间的关联关系
* 当when条件满足时,必须满足must条件否则校验不通过
* 注意:如果解析spel表达式错误将抛出异常
* @author wangzhen
*/
@Documented
@Constraint(validatedBy = {MultiFieldAssociationCheckValidator.class })
@Target({TYPE_USE })
@Retention(RUNTIME)
@Repeatable(MultiFieldAssociationCheck.List.class)
public @interface MultiFieldAssociationCheck {
/**
* 错误信息描述,必填
*/
String message();
/**
* 分组校验
*/
Class<?>[] groups() default { };
/**
* 负载
*/
Class<? extends Payload>[] payload() default { };
/**
* 当什么条件下校验,必须是一个spel表达式
*/
String when();
/**
* 必须满足什么条件,必须是一个spel表达式
*/
String must();
@Target({TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface List {
MultiFieldAssociationCheck[] value();
}
}
第二步:编写注解校验类
/**
* 多属性关联校验注解的实现类
*/
public class MultiFieldAssociationCheckValidator implements ConstraintValidator<MultiFieldAssociationCheck, Object> {
private static final String SPEL_TEMPLATE = "%s%s%s";
private static final String SPEL_PREFIX = "#{";
private static final String SPEL_SUFFIX = "}";
private String when;
private String must;
@Override
public void initialize(MultiFieldAssociationCheck constraintAnnotation) {
this.when = constraintAnnotation.when();
this.must = constraintAnnotation.must();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (StringUtils.isBlank(when) || StringUtils.isBlank(must)) {
return true;
}
Map<String, Object> spelMap = getSpelMap(value);
//when属性是一个spel表达式,执行这个表达式可以得到一个boolean值
boolean whenCheck = Boolean.parseBoolean(SpelUtils.parseSpel(String.format(SPEL_TEMPLATE, SPEL_PREFIX, when, SPEL_SUFFIX), spelMap));
if (whenCheck) {
//判断must是否满足条件
boolean mustCheck = Boolean.parseBoolean(SpelUtils.parseSpel(String.format(SPEL_TEMPLATE, SPEL_PREFIX, must, SPEL_SUFFIX), spelMap));
if (!mustCheck) {
//获取注解中的message属性值
String message = context.getDefaultConstraintMessageTemplate();
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
return false;
}
}
return true;
}
@SneakyThrows
private Map<String,Object> getSpelMap(Object value){
Field[] declaredFields = value.getClass().getDeclaredFields();
Map<String,Object> spelMap = new HashMap<>();
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
//将对象中的属性名和属性值放入map中
spelMap.put(declaredField.getName(),declaredField.get(value));
}
return spelMap;
}
类中依赖的SpelUtils.parseSpel方法
public static String parseSpel( String spel, Map<String, Object> map) {
if (StringUtils.isBlank(spel)) {
return "";
} else {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariables(map);
context.addPropertyAccessor(new MapAccessor());
context.addPropertyAccessor(new BeanFactoryAccessor());
return (String)parser.parseExpression(spel, new TemplateParserContext()).getValue(context, String.class);
}
}
第三步:使用注解,这个注解使用在类上
@Data
@MultiFieldAssociationCheck.List(
value = {
@MultiFieldAssociationCheck(when = "#type.equals('定时发送')", must = "#sendTime != null",message = "当type为定时发送时,必须填写发送时间"),
@MultiFieldAssociationCheck(when = "!#type.equals('草稿')", must = "#sendContent != null",message = "当type为不是草稿时,sendContent必须有值"),
@MultiFieldAssociationCheck(when = "#type.equals('立即发送')", must = "'123'.equals(#sendContent.content)",message = "当type为立即发送时,sendContent的content属性必须为123")
}
)
public class TestPo {
private String type;
//当type为定时发送时,必须填写发送时间,当type为立即发送时,可以不填发送时间
private Date sendTime;
@Valid
//当type为草稿时,sendContent可以为空,否则必须有值
private SendContent sendContent;
}
注意:spring表达式平时不太常用,一定要好好检查避免出现异常。
版权归原作者 zhen_csdn 所有, 如有侵权,请联系我们删除。