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错误、表单值会随请求结束被清空,跳转后的页面无法获取错误标记。
修复步骤
- 调整控制器方法的参数顺序,将
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"; } - 补全
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; } } - 修正金额校验的正则逻辑
当前使用的金额正则[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
相关产品推荐
相关产品推荐

