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

Spring MVC自定义BindingResult导致日期格式异常求助

问题根源分析

你的问题出在新建的BeanPropertyBindingResult完全丢失了原BindingResult中的绑定上下文信息:

  1. 原BindingResult不仅包含@Valid触发的验证错误,还包含数据绑定阶段的错误(比如日期格式转换失败的TypeMismatch错误),以及字段绑定的状态上下文(比如字段的原始输入值、绑定失败的细节)。
  2. 你只提取了getFieldErrors()(仅字段级验证错误),然后新建了一个空的BeanPropertyBindingResult,再把排序后的错误加进去——这个新对象完全没有继承原BindingResult的绑定状态,导致Spring MVC无法正确识别created字段的绑定错误,进而表现为格式异常。
  3. 同时,你忽略了原BindingResult中的全局错误、绑定错误(比如日期转换错误),这些错误没有被加入到新的BindingResult中,也会导致字段状态异常。

修复方案

不要新建BindingResult对象,直接修改原对象的错误顺序,或者完整复制原对象的所有状态后再调整错误顺序:

方案一:直接修改原BindingResult的错误顺序

@RequestMapping(value = {"create"}, method = RequestMethod.POST)
public String create(ModelMap modelMap, HttpSession session, RedirectAttributes ra, 
                     @ModelAttribute("product") @Valid Product product, BindingResult bindingResult) {
    if(bindingResult.hasErrors()) {
        // 获取所有错误并按规则排序
        List<ObjectError> sortedErrors = customErrorMessageOrder(bindingResult.getAllErrors());
        
        // 清空原错误列表,添加排序后的错误
        bindingResult.getAllErrors().clear();
        bindingResult.addAllErrors(new AbstractBindingResult() {
            @Override
            protected Object getActualBean() {
                return product;
            }

            @Override
            public String getObjectName() {
                return "product";
            }

            @Override
            public List<ObjectError> getAllErrors() {
                return sortedErrors;
            }
        });

        modelMap.put("categories", categoryService.findAll());
        return "admin/product/create";
    }

    return "redirect:/admin/product/index";
}

private List<ObjectError> customErrorMessageOrder(List<ObjectError> allErrors){
    // 筛选NotEmpty类字段错误
    List<ObjectError> notEmptyErrors = allErrors.stream()
            .filter(e -> e instanceof FieldError 
                    && ("NotEmpty".equals(((FieldError) e).getCode()) 
                        || "password.notEmpty".equals(((FieldError) e).getCode())))
            .collect(Collectors.toList());
    
    if(notEmptyErrors.isEmpty()) {
        return allErrors;
    }

    // 筛选剩余错误:排除已在NotEmpty列表中的同字段错误
    List<ObjectError> otherErrors = allErrors.stream()
            .filter(e -> !(e instanceof FieldError) 
                    || notEmptyErrors.stream().noneMatch(ne -> ((FieldError) ne).getField().equals(((FieldError) e).getField())))
            .collect(Collectors.toList());
    
    // 合并:NotEmpty错误优先,其余错误在后
    notEmptyErrors.addAll(otherErrors);
    return notEmptyErrors;
}

方案二:完整复制原BindingResult状态后调整顺序

如果必须新建BindingResult,要先复制原对象的所有绑定状态和错误,再调整顺序:

@RequestMapping(value = {"create"}, method = RequestMethod.POST)
public String create(ModelMap modelMap, HttpSession session, RedirectAttributes ra, 
                     @ModelAttribute("product") @Valid Product product, BindingResult bindingResult) {
    if(bindingResult.hasErrors()) {
        // 新建BindingResult并完整复制原对象的所有状态和错误
        BeanPropertyBindingResult newBindingResult = new BeanPropertyBindingResult(product, "product");
        newBindingResult.addAllErrors(bindingResult);
        
        // 获取排序后的错误列表
        List<ObjectError> sortedErrors = customErrorMessageOrder(newBindingResult.getAllErrors());
        
        // 清空原有错误,添加排序后的错误
        newBindingResult.getAllErrors().clear();
        newBindingResult.addAllErrors(new AbstractBindingResult() {
            @Override
            protected Object getActualBean() {
                return product;
            }

            @Override
            public String getObjectName() {
                return "product";
            }

            @Override
            public List<ObjectError> getAllErrors() {
                return sortedErrors;
            }
        });
        
        modelMap.put("org.springframework.validation.BindingResult.product", newBindingResult);
        modelMap.put("categories", categoryService.findAll());
        return "admin/product/create";
    }

    return "redirect:/admin/product/index";
}

// 同方案一的customErrorMessageOrder方法
private List<ObjectError> customErrorMessageOrder(List<ObjectError> allErrors){
    List<ObjectError> notEmptyErrors = allErrors.stream()
            .filter(e -> e instanceof FieldError 
                    && ("NotEmpty".equals(((FieldError) e).getCode()) 
                        || "password.notEmpty".equals(((FieldError) e).getCode())))
            .collect(Collectors.toList());
    
    if(notEmptyErrors.isEmpty()) {
        return allErrors;
    }

    List<ObjectError> otherErrors = allErrors.stream()
            .filter(e -> !(e instanceof FieldError) 
                    || notEmptyErrors.stream().noneMatch(ne -> ((FieldError) ne).getField().equals(((FieldError) e).getField())))
            .collect(Collectors.toList());
    
    notEmptyErrors.addAll(otherErrors);
    return notEmptyErrors;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 17:42:42