You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring 4.3.14.RELEASE中如何在自定义验证器中实现级联验证?

如何在自定义Map验证器中实现级联验证(像@Valid一样验证内部对象)

我在Spring 4.3.14.RELEASE项目的MVC层中,需要验证Map<String, List<InnerObj>>类型的对象。我已经编写了自定义验证器和对应的注解,但不知道怎么让验证规则像@Valid注解一样,级联验证内部的List集合及InnerObj对象。

我的自定义验证器代码:

public class MapValidator implements ConstraintValidator<ValidMap, Map<String, List<InnerObj>>> {
    @Override
    public boolean isValid(final Map<String, List<InnerObj>> map, final ConstraintValidatorContext context) {
        if (map == null || map.size() == 0) {
            return false;
        }
        // 遍历所有对象并执行验证
        return true;
    }
}

对应的注解:

@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = MapValidator.class)
public @interface ValidMap {
    String message() default "valid.map";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

解决方案

我来帮你搞定这个级联验证的需求。要让自定义Map验证器像@Valid一样递归验证内部的InnerObj,核心是在验证器里手动触发JSR-303的验证逻辑,并把内部对象的错误信息整合到当前的验证上下文里,这样这些错误会被Spring MVC的BindingResult正常收集。具体步骤如下:

1. 在自定义验证器中注入Validator实例

首先,我们需要在MapValidator里拿到JSR-303的Validator对象,Spring会自动帮我们注入这个实例:

import javax.validation.Validator;
import javax.validation.ConstraintViolation;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Set;

public class MapValidator implements ConstraintValidator<ValidMap, Map<String, List<InnerObj>>> {

    @Autowired
    private Validator validator;

    @Override
    public void initialize(ValidMap constraintAnnotation) {
        // 这里可以放初始化逻辑,比如读取注解参数,不需要的话留空就行
    }

    @Override
    public boolean isValid(final Map<String, List<InnerObj>> map, final ConstraintValidatorContext context) {
        if (map == null || map.size() == 0) {
            return false;
        }

        boolean isMapValid = true;
        // 先关闭默认的错误提示,避免重复输出默认消息
        context.disableDefaultConstraintViolation();

        // 遍历Map里的每一组键值对
        for (Map.Entry<String, List<InnerObj>> entry : map.entrySet()) {
            String mapKey = entry.getKey();
            List<InnerObj> innerObjList = entry.getValue();

            // 先校验List本身是否为空
            if (innerObjList == null) {
                context.buildConstraintViolationWithTemplate("列表[key: " + mapKey + "]不能为空")
                        .addPropertyNode(mapKey)
                        .addConstraintViolation();
                isMapValid = false;
                continue;
            }

            // 遍历List里的每个InnerObj,执行级联验证
            for (int objIndex = 0; objIndex < innerObjList.size(); objIndex++) {
                InnerObj innerObj = innerObjList.get(objIndex);
                // 触发InnerObj上的所有JSR-303验证规则
                Set<ConstraintViolation<InnerObj>> objViolations = validator.validate(innerObj);

                if (!objViolations.isEmpty()) {
                    isMapValid = false;
                    // 把每个InnerObj的错误信息添加到上下文,同时构建清晰的错误路径
                    for (ConstraintViolation<InnerObj> violation : objViolations) {
                        context.buildConstraintViolationWithTemplate(violation.getMessage())
                                // 错误路径格式:map字段名[mapKey][objIndex].innerObj字段名
                                .addPropertyNode(mapKey)
                                .addPropertyNode(String.valueOf(objIndex))
                                .addPropertyNode(violation.getPropertyPath().toString())
                                .addConstraintViolation();
                    }
                }
            }
        }

        return isMapValid;
    }
}

2. 确保InnerObj上的验证注解生效

你的InnerObj类需要标注标准的JSR-303验证注解,这样上面的validator.validate()才能检测到错误,比如:

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Min;

public class InnerObj {
    @NotNull(message = "名称不能为空")
    private String name;

    @Min(value = 18, message = "年龄不能小于18岁")
    private Integer age;

    // 别忘了添加getter和setter方法
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
}

3. 在目标Map字段上使用自定义注解

最后,在你需要验证的Map字段上标注@ValidMap即可,不需要额外加@Valid,因为我们已经在自定义验证器里实现了级联逻辑:

public class MyRequestDto {
    @ValidMap
    private Map<String, List<InnerObj>> dataMap;

    // getter和setter
    public Map<String, List<InnerObj>> getDataMap() { return dataMap; }
    public void setDataMap(Map<String, List<InnerObj>> dataMap) { this.dataMap = dataMap; }
}

一些注意事项

  • 如果你的项目没有用Spring的依赖注入,也可以通过Validation.buildDefaultValidatorFactory().getValidator()手动获取Validator实例,但在Spring环境下注入是更优雅的方式。
  • 构建错误路径很重要,这样前端能准确知道是Map里哪个key下的第几个InnerObj的哪个字段出了问题。
  • 关闭默认约束违反消息是为了避免出现重复的错误提示,确保只有我们自定义的错误信息被输出。

内容的提问来源于stack exchange,提问作者Vasiliy Tungusov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 06:55:45