You need to enable JavaScript to run this app. Spring Validation校验ExchangeCalculatorBuffer时为何触发400白屏?
优惠活动
大模型
产品
解决方案
定价
更多

Spring Validation校验ExchangeCalculatorBuffer时为何触发400白屏?

400白屏问题根因与修复

触发原因

从抛出的BindException异常栈可以判定,问题是三个核心问题叠加导致的:

  • 违反Spring MVC校验参数的强制顺序规则
    Spring MVC的强制要求为:承接校验结果的BindingResult/Errors参数,必须紧邻被@Valid标注的模型参数,中间不能插入其他任何类型的参数。
    你当前的控制器方法签名将Model插在被校验的ExchangeCalculatorBuffer和BindingResult中间,Spring参数解析器无法识别到承接校验结果的意图,不会自动将错误信息填充到你定义的BindingResult变量中,直接把校验异常抛到全局层面,触发未配置/error映射的白屏400页面。
  • 模型类不满足Spring参数绑定的基础要求
    你定义的ExchangeCalculatorBuffer仅实现了全参构造,没有公共无参构造、没有getter/setter方法。Spring完成请求参数绑定时,需要先通过无参构造实例化对象,再通过setter方法将请求参数注入对应字段,这部分结构缺失也会直接触发绑定异常。
  • 重定向逻辑的固有信息丢失问题
    就算调整了参数顺序,校验失败后直接返回"redirect:home"也会丢失所有错误信息:重定向是客户端发起的全新独立请求,原请求域中存储的BindingResult错误、表单值会随请求结束被清空,跳转后的页面无法获取错误标记。

修复步骤

  1. 调整控制器方法的参数顺序,将BindingResult移到被校验模型参数的紧后位置
    @PostMapping("/count")
    public String count(
        final @ModelAttribute("exchange") @Valid ExchangeCalculatorBuffer exchangeCalculatorBuffer,
        BindingResult result,
        Model model
    ) {
        if (result.hasErrors()) {
            // 直接渲染home页面,不做重定向,保证错误信息保留在请求域
            model.addAttribute("rates", jsonManager.getKeys());
            return "home";
        }
        // 原有校验通过后的计算逻辑保持不变
        ExchangeCalculator exchangeCalculator = new ExchangeCalculator(
                new CashAmountWithCurrency(exchangeCalculatorBuffer.getInitAmount(), exchangeCalculatorBuffer.getInitCurrency()),
                new CashAmountWithCurrency("1.0000", exchangeCalculatorBuffer.getTargetCurrency())
        );
        model.addAttribute("result", "result: "+exchangeCalculator.count(jsonManager.getMap()));
        model.addAttribute("exchange", new ExchangeCalculatorBuffer());
        model.addAttribute("rates", jsonManager.getKeys());
        return "home";
    }
    
  2. 补全ExchangeCalculatorBuffer的基础结构,满足参数绑定要求
    public class ExchangeCalculatorBuffer {
        private String initAmount;
        private String initCurrency;
        private String targetCurrency;
    
        // 补无参构造,供Spring反射实例化对象
        public ExchangeCalculatorBuffer() {}
    
        // 保留原有全参构造
        public ExchangeCalculatorBuffer(String initAmount, String initCurrency, String targetCurrency) {
            this.initAmount = initAmount;
            this.initCurrency = initCurrency;
            this.targetCurrency = targetCurrency;
        }
    
        // 补全getter/setter,供Spring注入请求参数
        public String getInitAmount() {
            return initAmount;
        }
    
        public void setInitAmount(String initAmount) {
            this.initAmount = initAmount;
        }
    
        public String getInitCurrency() {
            return initCurrency;
        }
    
        public void setInitCurrency(String initCurrency) {
            this.initCurrency = initCurrency;
        }
    
        public String getTargetCurrency() {
            return targetCurrency;
        }
    
        public void setTargetCurrency(String targetCurrency) {
            this.targetCurrency = targetCurrency;
        }
    }
    
  3. 修正金额校验的正则逻辑
    当前使用的金额正则[0-9]*.[0-9]*中,.是正则语法里匹配任意单个字符的元字符,需要转义后才能匹配小数点,修正为:
    AMOUNT("[0-9]*\\.[0-9]*", "err_code", "Wrong cash value")
    

可选优化

如果确实需要校验失败后通过重定向跳转页面,不能直接使用普通重定向返回字符串,需要将错误、表单值放到RedirectAttributes的闪屏属性中,实现跨请求传递信息:

@PostMapping("/count")
public String count(
    final @ModelAttribute("exchange") @Valid ExchangeCalculatorBuffer exchangeCalculatorBuffer,
    BindingResult result,
    RedirectAttributes redirectAttributes
) {
    if (result.hasErrors()) {
        // 将校验错误、表单值、汇率列表放到闪屏域
        redirectAttributes.addFlashAttribute(BindingResult.MODEL_KEY_PREFIX + "exchange", result);
        redirectAttributes.addFlashAttribute("exchange", exchangeCalculatorBuffer);
        redirectAttributes.addFlashAttribute("rates", jsonManager.getKeys());
        return "redirect:home";
    }
    // 校验通过后的逻辑同样改用重定向
    ExchangeCalculator exchangeCalculator = new ExchangeCalculator(
            new CashAmountWithCurrency(exchangeCalculatorBuffer.getInitAmount(), exchangeCalculatorBuffer.getInitCurrency()),
            new CashAmountWithCurrency("1.0000", exchangeCalculatorBuffer.getTargetCurrency())
    );
    redirectAttributes.addFlashAttribute("result", "result: "+exchangeCalculator.count(jsonManager.getMap()));
    redirectAttributes.addFlashAttribute("exchange", new ExchangeCalculatorBuffer());
    redirectAttributes.addFlashAttribute("rates", jsonManager.getKeys());
    return "redirect:home";
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.01 11:54:04