Spring中如何重定向至含@RequestBody参数的API并传递实例?
首先得明确一个核心限制:HTTP的重定向(3xx状态码)默认会触发浏览器发起GET请求,而且没法携带请求体,而你的/showAbc是POST接口,还依赖@RequestBody获取数据,所以直接用Spring的redirect:/showAbc肯定行不通。下面给你几个实用的解决方案,适配不同的场景:
方案一:前端中转(最贴合原接口设计)
既然/showBcd是被UI调用的,我们可以让/showBcd返回一个临时页面,通过页面内的JS自动发起POST请求到/showAbc,把Abc实例序列化为JSON作为请求体传递。
后端代码(/showBcd)
@RequestMapping(value = "/showBcd", method = RequestMethod.GET) public String showBcd(Model model) { // 从Repository获取Abc实例 Abc abcInstance = abcRepository.findById(1L).orElseThrow(() -> new RuntimeException("Abc not found")); // 把实例放到Model里,供前端页面获取 model.addAttribute("abcInstance", abcInstance); // 返回一个专门用于中转的视图 return "redirect-post"; }
中转页面(redirect-post.html,以Thymeleaf为例)
这个页面会自动执行JS,发送POST请求到目标接口:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <script th:inline="javascript"> // 获取后端传入的Abc实例 const abcData = /*[[${abcInstance}]]*/ {}; // 发送POST请求到/showAbc fetch('/showAbc', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(abcData), }) .then(response => { if (response.ok) { // 请求成功后,跳转到/showAbc返回的页面 window.location.href = response.url; } else { // 处理错误情况 window.location.href = '/error'; } }) .catch(error => { console.error('Error:', error); window.location.href = '/error'; }); </script> </body> </html>
优点:完全符合原/showAbc接口的POST+@RequestBody设计,浏览器地址栏最终会变成/showAbc,用户体验一致。
缺点:需要新增一个中转页面和JS代码,适合UI调用的场景。
方案二:修改/showAbc接口兼容两种参数来源
如果不想新增前端页面,可以调整/showAbc接口,让它同时支持从@RequestBody和Flash属性/Model属性获取Abc实例,这样既保留原UI的POST调用方式,又能支持重定向过来的请求。
修改后的/showAbc接口
@RequestMapping(value = "/showAbc", method = {RequestMethod.POST, RequestMethod.GET}) public ModelAndView showAbc( @RequestBody(required = false) Abc abcFromBody, @ModelAttribute(required = false) Abc abcFromModel) { // 优先取RequestBody里的实例,没有则取Model/Flash属性里的 Abc targetAbc = abcFromBody != null ? abcFromBody : abcFromModel; if (targetAbc == null) { // 处理参数为空的异常情况 return new ModelAndView("error", "message", "Abc instance is missing"); } // 执行原有业务逻辑 // doSomething(targetAbc); return new ModelAndView("abc-detail", "abc", targetAbc); }
/showBcd接口代码
@RequestMapping(value = "/showBcd", method = RequestMethod.GET) public String showBcd(RedirectAttributes redirectAttributes) { Abc abcInstance = abcRepository.findById(1L).orElseThrow(() -> new RuntimeException("Abc not found")); // 把Abc实例放到Flash属性里,重定向后会自动传入目标接口的Model redirectAttributes.addFlashAttribute("abc", abcInstance); // 重定向到/showAbc(此时浏览器会发起GET请求) return "redirect:/showAbc"; }
优点:不需要前端修改,后端内部兼容两种调用方式。
缺点:需要调整原接口的请求方法(增加GET支持),并且要处理参数来源的优先级问题;如果Abc是复杂对象,不要用redirectAttributes.addAttribute()(会把对象拼到URL参数里,长度有限且不安全),一定要用Flash属性。
方案三:内部转发(不改变地址栏)
如果不需要浏览器地址栏切换到/showAbc,只是服务器内部的请求跳转,可以用forward:前缀代替重定向,这样可以直接把Abc实例放到请求属性中传递。
/showBcd接口代码
@RequestMapping(value = "/showBcd", method = RequestMethod.GET) public String showBcd(HttpServletRequest request) { Abc abcInstance = abcRepository.findById(1L).orElseThrow(() -> new RuntimeException("Abc not found")); // 把实例放到请求属性中 request.setAttribute("abcInstance", abcInstance); // 内部转发到/showAbc return "forward:/showAbc"; }
修改后的/showAbc接口
@RequestMapping(value = "/showAbc", method = {RequestMethod.POST, RequestMethod.GET}) public ModelAndView showAbc( @RequestBody(required = false) Abc abcFromBody, @RequestAttribute(required = false) Abc abcFromRequest) { Abc targetAbc = abcFromBody != null ? abcFromBody : abcFromRequest; if (targetAbc == null) { return new ModelAndView("error", "message", "Abc instance is missing"); } // doSomething(targetAbc); return new ModelAndView("abc-detail", "abc", targetAbc); }
优点:不需要前端参与,服务器内部完成跳转,性能更好。
缺点:浏览器地址栏会保持/showBcd的地址,不符合“重定向”的地址栏变更要求,适合对地址栏无要求的场景。
内容的提问来源于stack exchange,提问作者aayoustic

