使用Ajax跳转视图时如何保留Controller的Model与BindingResult
问题分析
你当前的核心问题是:Ajax请求和location.href跳转是两个完全独立的HTTP请求,第一个POST请求中的Model和BindingResult仅属于该请求的上下文,新的GET请求无法直接继承这些数据,因此会出现数据丢失的情况。
下面给出几种可行的解决方案:
方案一:用Spring闪存属性(Flash Attributes)跨请求传递数据
Spring MVC提供的RedirectAttributes可以临时存储数据,这些数据会被放到会话中,仅在下一次请求可用,之后自动清除,完美适配重定向场景。
修改控制器代码
@PostMapping("/example") public String example( @RequestPart("formRequest") @Validated FormRequest formRequest, BindingResult bindingResult, @RequestPart(value = "file", required = false) MultipartFile file, @RequestPart(value = "files", required = false) MultipartFile[] files, RedirectAttributes redirectAttributes ) { if (bindingResult.hasErrors()) { log.warn("bindingResult={}", bindingResult); // 存储表单数据和状态到闪存属性 redirectAttributes.addFlashAttribute("formRequest", formRequest); redirectAttributes.addFlashAttribute("formStatus", FormStatus.CREATE); // 存储错误信息(BindingResult本身无法直接闪存,需转成错误列表) redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors()); // 直接返回重定向指令,无需前端手动跳转 return "redirect:/example"; } // 验证通过后的逻辑,比如跳转到成功页 return "redirect:/success"; }
前端适配(如果仍需用Ajax提交)
$.ajax({ url: url, type: "POST", data: formData, enctype: "multipart/form-data", contentType: false, processData: false, success: function (response, status, xhr) { // 识别重定向状态码,跳转至目标页面 if (xhr.status === 302) { location.href = xhr.getResponseHeader("Location"); } } });
补充GET请求控制器
在渲染表单的GET接口中,直接从Model获取闪存属性即可:
@GetMapping("/example") public String showForm(Model model) { // 若没有闪存数据,初始化空表单 if (!model.containsAttribute("formRequest")) { model.addAttribute("formRequest", new FormRequest(null, "", "", "", null, null, new ArrayList<>(), new ArrayList<>(), "")); } // 处理表单状态 model.addAttribute("formStatus", model.getAttribute("formStatus") != null ? model.getAttribute("formStatus") : FormStatus.CREATE); return "example-form"; }
方案二:前端直接处理错误,无需跳转页面
这种方式体验更好,不用跳转页面,直接在当前页保留用户输入并渲染错误信息。
修改控制器代码
@PostMapping("/example") @ResponseBody public Map<String, Object> example( @RequestPart("formRequest") @Validated FormRequest formRequest, BindingResult bindingResult, @RequestPart(value = "file", required = false) MultipartFile file, @RequestPart(value = "files", required = false) MultipartFile[] files ) { Map<String, Object> response = new HashMap<>(); if (bindingResult.hasErrors()) { log.warn("bindingResult={}", bindingResult); response.put("formRequest", formRequest); response.put("formStatus", FormStatus.CREATE); response.put("errors", bindingResult.getAllErrors()); response.put("success", false); return response; } response.put("success", true); response.put("url", "/success"); return response; }
修改前端Ajax逻辑
$.ajax({ url: url, type: "POST", data: formData, enctype: "multipart/form-data", contentType: false, processData: false, success: function (response) { if (response.success) { // 验证通过,跳转成功页 location.href = response.url; } else { // 恢复用户输入的表单数据 $("#content1").val(response.formRequest.content1); $("#content2").val(response.formRequest.content2); // ... 其他表单字段同理 // 渲染错误信息 let errorHtml = ""; response.errors.forEach(error => { errorHtml += `<div class="error-text">${error.defaultMessage}</div>`; }); $("#error-container").html(errorHtml); } } });
方案三:用Session临时存储数据(不推荐)
如果必须跳转,可以把数据存到Session中,但需注意用完清除,避免Session污染:
控制器修改
@PostMapping("/example") @ResponseBody public Map<String, Object> example( @RequestPart("formRequest") @Validated FormRequest formRequest, BindingResult bindingResult, @RequestPart(value = "file", required = false) MultipartFile file, @RequestPart(value = "files", required = false) MultipartFile[] files, HttpSession session ) { Map<String, Object> response = new HashMap<>(); if (bindingResult.hasErrors()) { log.warn("bindingResult={}", bindingResult); session.setAttribute("formRequest", formRequest); session.setAttribute("formStatus", FormStatus.CREATE); session.setAttribute("errors", bindingResult.getAllErrors()); response.put("url", "/example"); return response; } return response; }
GET请求中读取并清除Session数据
@GetMapping("/example") public String showForm(Model model, HttpSession session) { // 读取并清除表单数据 Object formRequest = session.getAttribute("formRequest"); if (formRequest != null) { model.addAttribute("formRequest", formRequest); session.removeAttribute("formRequest"); } else { model.addAttribute("formRequest", new FormRequest(null, "", "", "", null, null, new ArrayList<>(), new ArrayList<>(), "")); } // 读取并清除表单状态 Object formStatus = session.getAttribute("formStatus"); if (formStatus != null) { model.addAttribute("formStatus", formStatus); session.removeAttribute("formStatus"); } else { model.addAttribute("formStatus", FormStatus.CREATE); } // 读取并清除错误信息 Object errors = session.getAttribute("errors"); if (errors != null) { model.addAttribute("errors", errors); session.removeAttribute("errors"); } return "example-form"; }
注意:这种方式存在多标签页并发干扰的风险,一般不推荐使用。
内容的提问来源于stack exchange,提问作者jadekim
相关产品推荐
相关产品推荐

