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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 13:17:21