07-SpringMVC请求与转发和重定向

一、传统 Servlet 方式的回顾

正如你所说,在传统 Servlet 中,我们使用原始的 API 来实现页面跳转。

1. 请求转发 (Forward)

  • 特点

    • 服务器内部完成,客户端浏览器无感知。
    • 浏览器地址栏的 URL 不会改变
    • 共享同一个 request 对象和 response 对象,因此数据可以通过 request.setAttribute() 传递。
    • 只能跳转到当前 Web 应用内的资源
  • 代码

    // 获取请求转发器,"/success" 是服务器内部的资源路径(URI)
    RequestDispatcher dispatcher = request.getRequestDispatcher("/success");
    // 调用 forward 方法完成转发
    dispatcher.forward(request, response);
    

2. 重定向 (Redirect)

  • 特点

    • 通过客户端浏览器重新发送请求
    • 服务器返回一个状态码(302)和一个 Location 响应头,浏览器接收到后会自动向新的 Location 发送请求。
    • 浏览器地址栏的 URL 会变为新的地址
    • 不共享 request 对象,因为是两次独立的请求。数据传递通常通过 URL 参数(如 ?key=value)或 Session。
    • 可以跳转到任意 URL,包括其他网站。
  • 代码

    // 重定向到站内资源:"/contextPath/success"
    response.sendRedirect("/myapp/success"); 
    // 需要加上应用上下文路径(Context Path),否则容易出错
    
    // 重定向到站外资源
    response.sendRedirect("https://www.baidu.com");
    

传统方式的痛点

  1. 代码繁琐:需要手动获取 RequestDispatcher 或调用 sendRedirect
  2. 地址易错:重定向时,如果跳转站内资源,必须考虑上下文路径(Context Path),写绝对路径非常麻烦。
  3. 理解成本:需要明确理解转发和重定向的原理差异。

二、Spring MVC 的方式

Spring MVC 的 DispatcherServlet 作为核心控制器,极大地简化了这两种操作。它通过解析控制器方法的返回值来实现跳转,统一了地址书写格式。

核心机制:解析视图名

Spring MVC 处理完业务后,会查看控制器方法的返回值,并将其解析为一个具体的视图(如 JSP 页面)或一个跳转指令。

1. 默认请求转发

  • 格式return "视图名";
  • 说明:这是最简单的方式。默认情况下,返回的字符串会被 Spring MVC 的 InternalResourceViewResolver(视图解析器)当作一个视图名,并将其拼接上前缀(如 /WEB-INF/views/)和后缀(如 .jsp),最终执行一个请求转发操作。
  • 示例
    @Controller
    @RequestMapping("/user")
    public class UserController {
        
        @PostMapping("/add")
        public String addUser(User user) {
            // ... 处理添加用户的逻辑
            // 默认使用请求转发,跳转到 /WEB-INF/views/success.jsp (假设配置了相应前后缀)
            return "success"; 
        }
    }
    

2. 显式请求转发

  • 格式return "forward:/路径";
  • 说明:使用 forward: 前缀。这告诉 Spring MVC,不要走视图解析器的拼接流程,而是直接使用 RequestDispatcher.forward() 方法转发到指定的服务器内部路径。这个路径必须是同一个 Web 应用下的。
  • 特点:地址栏不变,共享 request。
  • 示例
    @Controller
    @RequestMapping("/user")
    public class UserController {
        
        @GetMapping("/detail")
        public String showDetail() {
            // 显式地转发到另一个控制器的方法
            // 浏览器地址栏仍显示 /user/detail,但内容来自 /internal/api/userinfo
            return "forward:/internal/api/userinfo"; 
        }
    }
    

3. 显式重定向

  • 格式return "redirect:/路径";
  • 说明:使用 redirect: 前缀。这告诉 Spring MVC 发送一个重定向响应。最大的优点是:Spring MVC 会自动帮你加上当前应用的上下文路径(Context Path),你不再需要手动拼接。
  • 特点:地址栏改变,不共享 request(两次请求)。
  • 示例
    @Controller
    @RequestMapping("/order")
    public class OrderController {
        
        @PostMapping("/create")
        public String createOrder(Order order) {
            // ... 处理创建订单的逻辑
            // 重定向到 /order/list
            // 实际响应头为: Location: /你的应用上下文路径/order/list
            return "redirect:/order/list"; 
        }
    
        @GetMapping("/list")
        public String showOrders() {
            return "order-list";
        }
    }
    
    为什么要用重定向?:防止表单重复提交。用户刷新浏览器时,只会重新请求 GET /order/list,而不会再次提交 POST /order/create 的表单数据。

三、对比总结与最佳实践

特性 传统 Servlet Spring MVC Spring MVC 地址写法
请求转发 request.getRequestDispatcher(...).forward(...) return "forward:/uri"return "view-name" /path (应用内路径)
重定向 response.sendRedirect(...) return "redirect:/uri" /path (Spring 自动加 Context Path)
地址书写 重定向需手动处理 Context Path,易错 统一简化,无需关心 Context Path 极其简单清晰
代码简洁度 繁琐 非常简洁
可读性 较低 非常高

最佳实践建议

  1. POST-Redirect-GET 模式

    • 在处理完 POST 请求(如表单提交)后,务必使用重定向 (redirect:) 到一个 GET 请求的页面。这是 Web 开发的最佳实践,能有效避免重复提交问题。
    • 错误示范POST /add -> 转发到 success.jsp(刷新浏览器会重复提交表单)。
    • 正确示范POST /add -> 重定向到 /list -> GET /list -> 转发到 list.jsp
  2. 内部跳转用转发

    • 如果只是在一个请求内部,将处理流程委托给另一个组件(如另一个控制器),且不需要改变浏览器地址,使用 forward:
  3. 默认视图用默认转发

    • 直接 return "view-name"; 是最常用的方式,用于渲染一个普通的 JSP/Thymeleaf 等模板页面。

四、示例代码

假设应用上下文路径为 /myapp

@Controller
public class DemoController {

    // 1. 默认请求转发 (到 /WEB-INF/views/index.jsp)
    @GetMapping("/default")
    public String defaultForward() {
        return "index"; 
    }

    // 2. 显式请求转发到另一个控制器
    @GetMapping("/forward")
    public String explicitForward() {
        // 转发后,地址栏仍是 /myapp/forward
        return "forward:/api/data"; 
    }

    @GetMapping("/api/data")
    @ResponseBody
    public String getData() {
        return "Data from API";
    }

    // 3. 重定向
    @PostMapping("/submit")
    public String handleSubmit() {
        // 处理提交...
        // 重定向后,地址栏变为 /myapp/success
        return "redirect:/success"; 
    }

    @GetMapping("/success")
    public String successPage() {
        return "success"; // 转发到 success.jsp
    }
}
posted on 2025-09-14 20:45  笨忠  阅读(7)  评论(0)    收藏  举报