最近在项目中使用spring内置的校验框架时,出现了一个问题:当某字段校验不通过时,对应的message信息被返回了两次,相关代码如下:
控制层
@RequestMapping(value = "/list", method = RequestMethod.POST)
@ResponseBody
public JsonResponse<ResVO> getList(@RequestBody @Valid ReqVO reqVO) {
ResVO preOrderList = orgService.getList(reqVO);
return JsonResponse.success(preOrderList);
}
请求入参
@Data
public class ReqVO {
@NotNull(message = "机构编号不能为空")
private Long orgNo;
@NotNull(message = "操作员编号不能为空")
private Long operatorNo;
}
当我使用postman发送如下参数的请求时,
{
"orgNo": null,
"operatorNo": 123
}
返回的结果为:
{
"code": "1",
"msg": "机构编号不能为空;机构编号不能为空",
"data": null
}
此处的msg是经过全局异常捕获机制将校验失败抛出的异常解析后拼接的内容,理论上来说这里应该只返回一遍“机构编号不能为空”才对,很明显是有问题的。
经过各种debug,最终将关键代码定位到org.hibernate.validator.internal.engine.ValidatorImpl#validateConstraintsForDefaultGroup方法中:
private <U> void validateConstraintsForDefaultGroup(ValidationContext<?> validationContext, ValueContext<U, Object> valueContext) {
// 获取bean(这里指 ReqVO 对象)的元数据,包含需要校验的入参对象的域、方法、构造器等
final BeanMetaData<U> beanMetaData = valueContext.getCurrentBeanMetaData();
final Map<Class<?>, Class<?>> validatedInterfaces = new HashMap<>();
// 遍历 ReqVO 及其父类
for ( Class<? super U> clazz : beanMetaData.getClassHierarchy() ) {
BeanMetaData<? super U> hostingBeanMetaData = beanMetaDataManager.getBeanMetaData( clazz );
boolean defaultGroupSequenceIsRedefined = hostingBeanMetaData.defaultGroupSequenceIsRedefined();
// if the current class redefined the default group sequence, this sequence has to be applied to all the class hierarchy.
if ( defaultGroupSequenceIsRedefined ) {
Iterator<Sequence> defaultGroupSequence = hostingBeanMetaData.getDefaultValidationSequence( valueContext.getCurrentBean() );
Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getMetaConstraints();
while ( defaultGroupSequence.hasNext() ) {
for ( GroupWithInheritance groupOfGroups : defaultGroupSequence.next() ) {
boolean validationSuccessful = true;
for ( Group defaultSequenceMember : groupOfGroups ) {
validationSuccessful = validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz,
metaConstraints, defaultSequenceMember ) && validationSuccessful;
}
validationContext.markCurrentBeanAsProcessed( valueContext );
if ( !validationSuccessful ) {
break;
}
}
}
}
// 一般走这个分支
else {
// 获取直接约束,“直接”指当前类自身(不包含继承),即当前类中携带了@NotNull、@NotEmpty等注解的域、方法的信息
Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints();
validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints,
Group.DEFAULT_GROUP );
validationContext.markCurrentBeanAsProcessed( valueContext );
}
// all constraints in the hierarchy has been validated, stop validation.
if ( defaultGroupSequenceIsRedefined ) {
break;
}
}
}
debug观察获取到的metaConstraints变量的值:
预期这个list应该只有两个元素,即orgNo与operatorNo字段的NotNull校验,但现在多出了getOrgNo()与getOperatorNo()方法的NotNull校验。
故怀疑是get方法出了问题,ReqVO类使用的是lombok的@Data注解自动生成的get方法,去掉@Data注解,手动生成getter和setter,问题解决。原因可能是lombok在给各字段生成getter和setter方法时,携带了字段上方的注解信息,导致spring的校验框架出现重复校验的问题。
总结:使用@Valid或@Validated配合@NotNull、@NotEmpty等注解对参数进行校验时,不能使用lombok自动生成的getter和setter,否则会导致同一个参数被校验两次(字段本身一次与get方法一次),当校验不通过时会多出一次error结果。