乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 SpringMVC概述与DispatcherServlet

    • 本章导读:Spring MVC概述与DispatcherServlet
    • Spring MVC 是什么
    • MVC 设计模式
    • 前端控制器模式
    • DispatcherServlet
    • 核心组件协作
  • 第2章 控制器与请求映射

    • 本章导读:控制器与请求映射
    • Controller
    • RestController
    • RequestMapping
    • GetMapping
    • PostMapping
    • PutMapping
    • DeleteMapping
    • PathVariable
    • RESTful
    • 请求映射原理
  • 第3章 请求参数获取与转换

    • 本章导读:请求参数获取与转换
    • RequestParam
    • RequestBody
    • RequestHeader
    • CookieValue
    • Model
    • ModelAttribute
    • 数据绑定原理
    • 数据校验
  • 第4章 响应数据与视图解析

    • 本章导读:响应数据与视图解析
    • ResponseBody
    • ResponseEntity
    • ModelAndView
    • ViewResolver
    • HttpMessageConverter
    • forward与redirect
  • 第5章 拦截器过滤器与跨域

    • 本章导读:拦截器、过滤器与跨域
    • HandlerInterceptor
    • WebMvcConfigurer
    • CrossOrigin
    • 登录验证实战
  • 第6章 文件上传与异常处理

    • 本章导读:文件上传与异常处理
    • MultipartFile
    • 文件下载
    • ExceptionHandler
    • ControllerAdvice
    • RestControllerAdvice
    • ResponseStatus
  • 第7章 高级特性与最佳实践

    • 本章导读:高级特性与最佳实践
    • SessionAttributes
    • SessionAttribute
    • RedirectAttributes
    • MockMvc测试
    • 国际化
    • 最佳实践
  • 第8章 扩展与异步机制

    • 本章导读:扩展与异步机制
    • 异步请求处理
    • 自定义参数解析器
    • 内容协商

forward与redirect

本章是"响应数据与视图解析"章节的收尾,讲解 Spring MVC 中两种特殊的请求转发机制。前面章节中,Controller 方法返回视图名,由 ViewResolver 解析为模板文件并渲染。而返回 "forward:/path" 或 "redirect:/path" 时,Spring MVC 会触发完全不同的处理流程——前者是服务器内部转发,后者是客户端重定向。理解两者的差异,是避免表单重复提交、实现页面跳转的关键。


定义与作用

Spring MVC 支持在视图名前加特殊前缀,改变默认的视图解析行为:

forward: 前缀——服务器内部转发

return "forward:/employees/list";

服务器内部将请求转发给另一个 URL,客户端完全无感知。URL 地址栏不变,请求和响应对象在服务器内部传递。

redirect: 前缀——客户端重定向

return "redirect:/employees/list";

服务器返回 HTTP 302 响应,指示客户端重新发起请求到新的 URL。客户端地址栏会变化,是一次全新的 HTTP 请求。

生活类比:公司内部转接 vs 挂断重拨

想象飞翔科技的总机系统:

  • forward(内部转接):你拨打总机 8080,说"找技术部"。总机小姐(DispatcherServlet)直接把电话线插到技术部分机(另一个 Controller),你全程只拨了一次号,但通话对象变了。你的手机上显示的号码始终是 8080
  • redirect(挂断重拨):你拨打总机 8080,说"找技术部"。总机小姐说"技术部电话是 8081,请重拨"。你挂断电话,重新拨打 8081。手机上显示的号码变成了 8081

forward 是一次请求的服务器内部分发;redirect 是两次独立的 HTTP 请求。


核心原理

forward vs redirect 流程对比

核心差异:

维度forwardredirect
HTTP 请求次数1 次2 次
地址栏变化不变变化
请求属性共享共享(同一个 request)不共享(新 request)
刷新行为重复原请求重复目标 GET 请求
适用场景内部流程分发表单提交后跳转、跨域跳转
性能快(服务器内部)慢(两次往返)

适用位置与常用属性

forward: 和 redirect: 作为 Controller 方法的返回值 中的视图名前缀使用。

前缀语法说明
forward:forward:/path服务器内部转发到指定 URL
redirect:redirect:/path302 重定向到指定 URL
redirect:redirect:/path?key=value重定向时携带查询参数

Flash 属性传递

redirect 后请求属性不共享,但可以通过 Flash Attributes 传递一次性数据:

@PostMapping("/create")
public String create(RedirectAttributes redirectAttrs) {
    // 保存员工...
    redirectAttrs.addFlashAttribute("message", "员工创建成功");
    return "redirect:/employees/list";
}

Flash 属性存储在 Session 中,重定向后的第一次请求可用,读取后自动删除。


完整示例

场景

飞翔科技员工管理系统的"新增员工"功能:表单提交后,如果直接返回视图名,用户刷新页面会重复提交表单(导致重复创建员工)。架构师白歌要求使用 POST-Redirect-GET 模式 解决此问题。

代码实现

package com.feixiang.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.ArrayList;
import java.util.List;

@Controller
@RequestMapping("/employees")
public class EmployeeController {

    private final List<Employee> employees = new ArrayList<>();

    /**
     * 显示新增表单(GET)
     */
    @GetMapping("/new")
    public String showForm() {
        return "employee-form";  // 普通视图解析
    }

    /**
     * 处理表单提交(POST)
     * POST-Redirect-GET 模式:提交后重定向到列表页
     */
    @PostMapping
    public String createEmployee(@ModelAttribute Employee employee,
                                  RedirectAttributes redirectAttrs) {
        employees.add(employee);
        
        // Flash 属性:重定向后可用,读取一次即删除
        redirectAttrs.addFlashAttribute("message", 
            "员工 " + employee.getName() + " 创建成功");
        
        return "redirect:/employees/list";  // 重定向到列表页
    }

    /**
     * 员工列表(GET)
     */
    @GetMapping("/list")
    public String list(Model model) {
        model.addAttribute("employees", employees);
        return "employee-list";
    }

    /**
     * forward 示例:内部转发到列表
     */
    @GetMapping("/forward-list")
    public String forwardList() {
        return "forward:/employees/list";  // 服务器内部转发
    }

    /**
     * redirect 带查询参数
     */
    @GetMapping("/search")
    public String search(@RequestParam String name,
                          RedirectAttributes redirectAttrs) {
        redirectAttrs.addAttribute("keyword", name);  // 会附加到 URL
        return "redirect:/employees/list";
    }
}

// Employee.java
package com.feixiang.web;

public class Employee {
    private String name;
    private String department;
    private Integer salary;

    public Employee() {}
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getDepartment() { return department; }
    public void setDepartment(String department) { this.department = department; }
    public Integer getSalary() { return salary; }
    public void setSalary(Integer salary) { this.salary = salary; }
}

HTTP 请求示例

示例 1:POST-Redirect-GET 模式

# 第一步:POST 提交表单
curl -i -X POST "http://localhost:8080/employees" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "name=张三" \
     -d "department=技术部" \
     -d "salary=25000"

响应(第一步):

HTTP/1.1 302
Location: /employees/list

浏览器自动跟随重定向:

# 第二步:GET 列表页(浏览器自动发起)
curl "http://localhost:8080/employees/list"

响应(第二步):

<!DOCTYPE html>
<html>
<head><title>员工列表</title></head>
<body>
    <div class="alert">员工 张三 创建成功</div>  <!-- Flash 消息 -->
    <h1>员工列表</h1>
    <table>...</table>
</body>
</html>

此时用户刷新页面,只会重复 GET /employees/list,不会重复提交 POST 请求。

示例 2:forward 内部转发

curl -i "http://localhost:8080/employees/forward-list"

响应:

HTTP/1.1 200

客户端看到的 URL 仍然是 /employees/forward-list,但实际内容是 /employees/list 渲染的结果。地址栏不变。

示例 3:redirect 带查询参数

curl -i "http://localhost:8080/employees/search?name=张三"

响应:

HTTP/1.1 302
Location: /employees/list?keyword=张三

addAttribute 添加的参数会自动附加到重定向 URL 的查询字符串中。


易错场景与面试考点

误区一:POST 请求 forward 到 GET 处理方法的误解

错误认知:"return "forward:/employees/list" 可以把 POST 请求变成 GET 请求的内部调用"

纠正:forward 保持原请求方法不变。如果原请求是 POST,forward 到的目标也必须支持 POST。否则报错 405 Method Not Allowed。

@PostMapping("/create")
public String create() {
    return "forward:/employees/list";  // 错误!/employees/list 是 GET
}

解决:POST 提交后想显示列表,应该用 redirect,而不是 forward。

误区二:redirect 后丢失表单数据

现象:表单提交后 redirect 到成功页面,但成功页面无法显示刚才提交的数据。

纠正:redirect 是全新请求,原 request 中的属性全部丢失。解决方案:

  1. Flash Attributes(推荐):redirectAttrs.addFlashAttribute("key", value)
  2. URL 参数:redirectAttrs.addAttribute("key", value)(数据暴露在 URL 中,不适合敏感信息)
  3. 数据库/Session:将数据持久化,重定向后重新查询

误区三:forward 和 redirect 路径前忘记加 /

现象:return "forward:employees/list"(缺少前导 /),路径解析错误。

纠正:

  • 加 /:forward:/employees/list → 从应用根路径开始解析
  • 不加 /:forward:employees/list → 相对当前路径解析,容易出错

最佳实践:始终使用前导 /,确保路径从应用根目录开始。

面试高频:POST-Redirect-GET 模式是什么?

标准回答:

  1. 问题:用户提交表单(POST)后,如果直接返回页面,刷新浏览器会重复提交表单,导致数据重复创建
  2. 解决:POST 请求处理完后,不直接返回视图,而是 redirect 到一个 GET 页面
  3. 流程:POST 提交 → 服务器处理 → 302 重定向 → 浏览器 GET 新页面 → 显示结果
  4. 优点:刷新页面只会重复 GET 请求,不会重复提交数据
  5. 数据传递:使用 Flash Attributes 在重定向后传递一次性消息(如"操作成功")

小结

forward: 和 redirect: 是 Spring MVC 中改变默认视图解析行为的两个特殊前缀。forward 在服务器内部转发请求,地址栏不变;redirect 通知客户端重新请求,地址栏变化。

核心要点:

  • forward:1 次请求,共享 request,地址栏不变,适合内部流程分发
  • redirect:2 次请求,不共享 request,地址栏变化,适合表单提交后跳转
  • POST 提交后必须使用 redirect 防止重复提交(POST-Redirect-GET 模式)
  • Flash Attributes 用于在 redirect 后传递一次性数据
  • 路径始终使用前导 /,避免相对路径解析错误

本章与全局的关系:本章完成了"响应数据与视图解析"章节的讲解。从 @ResponseBody 的直接输出,到 ResponseEntity 的完整响应控制,再到 ViewResolver 的视图解析、ModelAndView 的动态视图,最后到 forward 与 redirect 的转发机制,形成了完整的响应数据流出链路。至此,Spring MVC 的请求-响应核心流程已全部覆盖。

上一页
HttpMessageConverter