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

嵌套列表对象Spring校验异常求助:NotReadablePropertyException

解决Spring Validator嵌套对象校验的NotReadablePropertyException问题

这个异常的核心原因很明确:你在BigObject的Errors上下文里直接校验SecondObject的属性,Spring会尝试在BigObject类中查找order、titol这些属性,显然找不到,所以抛出了属性不可读的异常。下面我给你两种可行的修复方案,以及指出你代码里的其他小问题。

方案一:使用嵌套路径(NestedPath)处理多层校验

Spring的Errors接口支持嵌套路径,可以让你在遍历嵌套对象时,动态切换校验的上下文路径。修改你的BigObjectValidator代码如下:

public class BigObjectValidator implements Validator {
    @Override
    public boolean supports(Class clazz) {
        return BigObject.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors e) {
        BigObject bigObject = (BigObject) obj;
        ValidationUtils.rejectIfEmptyOrWhitespace(e, "idObject", "empty.id");
        ValidationUtils.rejectIfEmptyOrWhitespace(e, "idLanguage", "empty.id");
        ValidationUtils.rejectIfEmptyOrWhitespace(e, "dateGeneration", "empty.id");

        List<FirstObject> firstObjects = bigObject.getFirstObject();
        if (firstObjects != null && !firstObjects.isEmpty()) {
            for (int i = 0; i < firstObjects.size(); i++) {
                FirstObject firstObject = firstObjects.get(i);
                // 推入FirstObject的嵌套路径
                e.pushNestedPath("firstObject[" + i + "]");
                if (firstObject.getSecondObject() != null && !firstObject.getSecondObject().isEmpty()) {
                    List<SecondObject> secondObjects = firstObject.getSecondObject();
                    for (int j = 0; j < secondObjects.size(); j++) {
                        SecondObject secondObject = secondObjects.get(j);
                        // 推入SecondObject的嵌套路径
                        e.pushNestedPath("secondObject[" + j + "]");
                        validateSecondObject(secondObject, e);
                        // 弹出当前嵌套路径,回到FirstObject的上下文
                        e.popNestedPath();
                    }
                }
                // 弹出FirstObject的嵌套路径,回到BigObject的上下文
                e.popNestedPath();
            }
        }
    }

    private void validateSecondObject(SecondObject secondObject, Errors e) {
        // 现在Errors的上下文是对应的secondObject,直接用属性名即可
        ValidationUtils.rejectIfEmptyOrWhitespace(e, "order", "order.empty");
        ValidationUtils.rejectIfEmptyOrWhitespace(e, "titol", "titol.empty");
        // 注意floatProperty是int类型,不能用rejectIfEmptyOrWhitespace,要判断是否为有效范围或者非默认值
        if (secondObject.getFloatProperty() <= 0) { // 假设需要大于0的有效值
            e.rejectValue("floatProperty", "floatProperty.invalid");
        }
    }
}

这里的关键是pushNestedPath和popNestedPath方法,它们会动态修改Errors的属性路径前缀,比如当你推入firstObject[0].secondObject[0]后,校验order时,实际查找的路径是firstObject[0].secondObject[0].order,完全匹配嵌套对象的结构。

方案二:为每个嵌套类单独编写Validator

这种方式更符合单一职责原则,维护性更好。你可以为FirstObject和SecondObject分别创建Validator,然后在BigObjectValidator中调用它们:

1. 创建SecondObjectValidator

public class SecondObjectValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return SecondObject.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        SecondObject secondObject = (SecondObject) target;
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "order", "order.empty");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "titol", "titol.empty");
        if (secondObject.getFloatProperty() <= 0) {
            errors.rejectValue("floatProperty", "floatProperty.invalid");
        }
    }
}

2. 创建FirstObjectValidator

public class FirstObjectValidator implements Validator {
    private final SecondObjectValidator secondValidator = new SecondObjectValidator();

    @Override
    public boolean supports(Class<?> clazz) {
        return FirstObject.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        FirstObject firstObject = (FirstObject) target;
        List<SecondObject> secondObjects = firstObject.getSecondObject();
        if (secondObjects != null && !secondObjects.isEmpty()) {
            for (int j = 0; j < secondObjects.size(); j++) {
                errors.pushNestedPath("secondObject[" + j + "]");
                secondValidator.validate(secondObjects.get(j), errors);
                errors.popNestedPath();
            }
        }
    }
}

3. 修改BigObjectValidator

public class BigObjectValidator implements Validator {
    private final FirstObjectValidator firstValidator = new FirstObjectValidator();

    @Override
    public boolean supports(Class<?> clazz) {
        return BigObject.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        BigObject bigObject = (BigObject) target;
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "idObject", "empty.id");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "idLanguage", "empty.id");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "dateGeneration", "empty.id");

        List<FirstObject> firstObjects = bigObject.getFirstObject();
        if (firstObjects != null && !firstObjects.isEmpty()) {
            for (int i = 0; i < firstObjects.size(); i++) {
                errors.pushNestedPath("firstObject[" + i + "]");
                firstValidator.validate(firstObjects.get(i), errors);
                errors.popNestedPath();
            }
        }
    }
}

这种方式把每个类的校验逻辑拆分出来,代码更清晰,后续修改某一层的校验规则也更方便。

额外注意:主程序中的代码错误

你的主程序代码里有几个明显的语法/逻辑错误,也会导致问题:

  • List<FirstObject> firstObj = ArrayList<FirstObject>; 应该是List<FirstObject> firstObj = new ArrayList<>();
  • object.getIdObject("something"); 这是调用getter方法,应该是object.setIdObject(1L);(因为idObject是Long类型)
  • object.getIdLanguage("En"); 同理,应该是object.setIdLanguage("En");
  • object.getDateGeneration("05-18-2018"); 应该是object.setDateGeneration(new SimpleDateFormat("MM-dd-yyyy").parse("05-18-2018"));(需要处理ParseException)
  • firstObj.set(1,secondObject); 你的firstObj是空的ArrayList,索引1不存在,应该用firstObj.add(secondObject);,然后把firstObj设置给BigObject:object.setFirstObject(firstObj);

修正这些小问题后,再结合上面的校验器方案,就能正常运行了。

内容的提问来源于stack exchange,提问作者Kordh Rufan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:27:21