一、传统 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");
传统方式的痛点:
- 代码繁琐:需要手动获取
RequestDispatcher或调用sendRedirect。 - 地址易错:重定向时,如果跳转站内资源,必须考虑上下文路径(Context Path),写绝对路径非常麻烦。
- 理解成本:需要明确理解转发和重定向的原理差异。
二、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 | 极其简单清晰 |
| 代码简洁度 | 繁琐 | 非常简洁 | |
| 可读性 | 较低 | 非常高 |
最佳实践建议:
-
POST-Redirect-GET 模式:
- 在处理完 POST 请求(如表单提交)后,务必使用重定向 (
redirect:) 到一个 GET 请求的页面。这是 Web 开发的最佳实践,能有效避免重复提交问题。 - 错误示范:
POST /add -> 转发到 success.jsp(刷新浏览器会重复提交表单)。 - 正确示范:
POST /add -> 重定向到 /list->GET /list -> 转发到 list.jsp。
- 在处理完 POST 请求(如表单提交)后,务必使用重定向 (
-
内部跳转用转发:
- 如果只是在一个请求内部,将处理流程委托给另一个组件(如另一个控制器),且不需要改变浏览器地址,使用
forward:。
- 如果只是在一个请求内部,将处理流程委托给另一个组件(如另一个控制器),且不需要改变浏览器地址,使用
-
默认视图用默认转发:
- 直接
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
}
}
浙公网安备 33010602011771号