深入理解Servlet中的转发与重定向
一、转发与重定向的本质区别:一次请求vs两次请求
1. 转发(Forward):服务器内部的"接力赛"
转发是服务器内部的资源跳转机制。当客户端发送请求到服务器后,服务器端的组件(如Servlet)处理部分逻辑后,将请求"转交给"另一个组件继续处理,最终由某个组件生成响应返回给客户端。
工作流程图:
客户端浏览器 → 发送请求 → 服务器
↓
Servlet A(处理部分逻辑)
↓(内部转发)
Servlet B(继续处理并生成响应)
↓
客户端浏览器 ← 返回响应 ← 服务器

核心特征:
- 整个过程只有一次HTTP请求 和一次HTTP响应
- 浏览器地址栏URL保持不变(显示初始请求的URL)
- 所有参与跳转的组件共享同一个
HttpServletRequest和HttpServletResponse对象 - 只能跳转到当前Web应用内部的资源(无法跳转到外部网站)
2. 重定向(Redirect):服务器指挥客户端的"二次访问"
重定向是服务器通知客户端重新发送请求的机制。服务器收到客户端请求后,会返回一个特殊的响应(状态码302),告诉客户端"你应该去访问另一个URL",客户端收到这个响应后,会立即向新URL发送第二次请求。
工作流程图:
客户端浏览器 → 发送请求1 → 服务器
↓
Servlet A(处理后返回重定向响应)
↓(响应包含新URL:状态码302)
客户端浏览器 ← 接收响应 ← 服务器
↓
客户端浏览器 → 发送请求2(访问新URL) → 服务器
↓
Servlet B(处理并生成响应)
↓
客户端浏览器 ← 返回响应 ← 服务器

核心特征:
- 整个过程包含两次独立的HTTP请求 和两次HTTP响应
- 浏览器地址栏URL会发生变化(显示最终访问的URL)
- 两次请求使用不同的
HttpServletRequest和HttpServletResponse对象(不共享数据) - 可以跳转到任意URL(包括当前Web应用内部、其他Web应用甚至外部网站)
二、核心API与基础用法
转发和重定向的实现方式在Servlet中非常简洁,但需要注意路径写法和调用时机。
1. 转发的实现:RequestDispatcher接口
转发通过HttpServletRequest对象获取RequestDispatcher实例,再调用其forward()方法实现:
java
// 语法:获取转发器并执行转发
RequestDispatcher dispatcher = request.getRequestDispatcher("目标资源路径");
dispatcher.forward(request, response);
// 简化写法
request.getRequestDispatcher("目标资源路径").forward(request, response);
路径规则:
-
绝对路径:以
/开头,代表当前Web应用的根目录(推荐使用)java// 转发到Web应用根目录下的/views/success.jsp request.getRequestDispatcher("/views/success.jsp").forward(request, response); -
相对路径:相对于当前Servlet的访问路径(容易出错,不推荐)
java// 当前Servlet路径为/user/login,转发到同目录下的success.jsp request.getRequestDispatcher("success.jsp").forward(request, response);
2. 重定向的实现:HttpServletResponse接口
重定向通过HttpServletResponse的sendRedirect()方法实现:
java
// 语法:指定重定向的目标URL
response.sendRedirect("目标URL");
路径规则:
-
相对路径:相对于当前请求的URL(不推荐,易混淆)
java// 当前URL为http://localhost:8080/app/user/login,重定向到同目录下的success.jsp response.sendRedirect("success.jsp"); // 实际跳转至http://localhost:8080/app/user/success.jsp -
绝对路径:
-
应用内绝对路径:以
/开头,代表当前Web应用的根目录,需拼接上下文路径java// 获取Web应用上下文路径(如/app) String contextPath = request.getContextPath(); // 重定向到/app/views/success.jsp response.sendRedirect(contextPath + "/views/success.jsp"); -
外部URL:直接写完整URL(协议+域名+路径)
java// 重定向到百度首页 response.sendRedirect("https://www.baidu.com");
-
三、实战对比:相同需求下的两种实现
为了更直观地感受转发和重定向的差异,我们通过"用户登录"场景分别实现两种跳转方式,并对比效果。
环境准备
-
开发工具:IntelliJ IDEA/Eclipse
-
服务器:Tomcat 9.0
-
项目结构:
webapp ├── login.html // 登录表单 ├── WEB-INF │ └── web.xml // 配置文件(可选,注解开发可省略) └── views ├── success.jsp // 登录成功页面 └── error.jsp // 登录失败页面
1. 转发实现登录流程
步骤1:登录表单(login.html)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<form action="/demo/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<button type="submit">登录</button>
</form>
</body>
</html>
步骤2:处理登录的Servlet(LoginServlet.java)
java
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置请求编码,解决中文乱码
request.setCharacterEncoding("UTF-8");
// 获取表单参数
String username = request.getParameter("username");
String password = request.getParameter("password");
// 简单验证(实际项目中应查询数据库)
if ("admin".equals(username) && "123456".equals(password)) {
// 登录成功:将用户名存入请求域,转发到success.jsp
request.setAttribute("username", username);
request.getRequestDispatcher("/views/success.jsp").forward(request, response);
} else {
// 登录失败:转发到error.jsp
request.setAttribute("message", "用户名或密码错误");
request.getRequestDispatcher("/views/error.jsp").forward(request, response);
}
}
}
步骤3:成功页面(success.jsp)
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录成功</title>
</head>
<body>
<h1>欢迎您,${username}!</h1>
<p>请求次数:1次(转发)</p>
<p>浏览器地址:<script>document.write(location.href)</script></p>
</body>
</html>
步骤4:失败页面(error.jsp)
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录失败</title>
</head>
<body>
<h1 style="color: red;">${message}</h1>
<p>请求次数:1次(转发)</p>
<p>浏览器地址:<script>document.write(location.href)</script></p>
</body>
</html>
转发实现的运行结果:
- 登录成功后,地址栏仍显示
http://localhost:8080/demo/login - 页面正确显示用户名(通过请求域传递数据成功)
- 刷新页面会弹出"是否重新提交表单"的提示(因为是同一次请求)
2. 重定向实现登录流程
步骤1:登录表单(与转发实现相同,复用login.html)
步骤2:处理登录的Servlet(RedirectLoginServlet.java)
java
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/redirectLogin")
public class RedirectLoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
// 获取上下文路径(用于构建绝对路径)
String contextPath = request.getContextPath();
if ("admin".equals(username) && "123456".equals(password)) {
// 登录成功:重定向无法通过请求域传数据,需用Session
HttpSession session = request.getSession();
session.setAttribute("username", username);
// 重定向到success.jsp
response.sendRedirect(contextPath + "/views/success.jsp");
} else {
// 登录失败:重定向到error.jsp,通过URL参数传递错误信息
response.sendRedirect(contextPath + "/views/error.jsp?message=用户名或密码错误");
}
}
}
步骤3:重定向版成功页面(success.jsp)
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录成功</title>
</head>
<body>
<h1>欢迎您,${sessionScope.username}!</h1>
<p>请求次数:2次(重定向)</p>
<p>浏览器地址:<script>document.write(location.href)</script></p>
</body>
</html>
步骤4:重定向版失败页面(error.jsp)
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录失败</title>
</head>
<body>
<h1 style="color: red;">${param.message}</h1>
<p>请求次数:2次(重定向)</p>
<p>浏览器地址:<script>document.write(location.href)</script></p>
</body>
</html>
重定向实现的运行结果:
- 登录成功后,地址栏显示
http://localhost:8080/demo/views/success.jsp(URL发生变化) - 页面通过Session获取用户名(请求域数据无法传递)
- 刷新页面不会弹出重新提交提示(第二次请求是全新的GET请求)
四、全方位对比:转发与重定向的核心差异
通过上面的实例,我们可以总结出转发和重定向在各个维度的差异:
| 对比维度 | 转发(Forward) | 重定向(Redirect) |
|---|---|---|
| 请求次数 | 1次(服务器内部处理) | 2次(客户端重新发送请求) |
| 地址栏URL | 不变(显示初始请求URL) | 变化(显示最终目标URL) |
| 数据共享 | 共享HttpServletRequest/Response对象,可通过请求域(setAttribute)传递数据 | 不共享对象,请求域数据失效,需用Session或URL参数传递 |
| 跳转范围 | 仅限当前Web应用内部 | 可跳转至任意URL(内部、外部均可) |
| 适用场景 | 同一请求的多组件协作(如MVC模式中Controller到View的跳转) | 不同请求的跳转(如登录后跳首页、退出登录) |
| 路径写法 | 相对当前Web应用根目录(无需上下文路径) | 需包含上下文路径(或完整URL) |
| 浏览器刷新影响 | 可能导致表单重复提交 | 无重复提交风险(第二次是GET请求) |
| 状态码 | 无特殊状态码(由最终组件决定) | 302(临时重定向)或301(永久重定向) |
五、避坑指南:常见问题与解决方案
在实际开发中,转发和重定向容易出现各种问题,以下是最常见的坑及解决方法:
1. 路径错误导致404异常
问题现象 :控制台报FileNotFoundException,浏览器显示404错误。
解决方案:
-
转发使用绝对路径时,以
/开头(代表Web应用根目录):java// 正确:从Web应用根目录查找资源 request.getRequestDispatcher("/views/success.jsp").forward(request, response); // 错误:路径缺少/,会从当前Servlet路径下查找 request.getRequestDispatcher("views/success.jsp").forward(request, response); -
重定向必须包含上下文路径:
java// 正确:拼接上下文路径 response.sendRedirect(request.getContextPath() + "/views/success.jsp"); // 错误:直接使用/开头会从服务器根目录查找(如Tomcat的webapps目录) response.sendRedirect("/views/success.jsp");
2. 数据传递失败
问题现象:在目标组件中无法获取传递的数据。
解决方案:
-
转发使用
request.setAttribute()存储数据,request.getAttribute()获取:java// 存储 request.setAttribute("user", user); // 获取 User user = (User) request.getAttribute("user"); -
重定向需使用Session或URL参数传递数据:
java// Session方式(适合敏感数据) request.getSession().setAttribute("user", user); // URL参数方式(适合简单非敏感数据) response.sendRedirect(contextPath + "/views/success.jsp?name=" + URLEncoder.encode("张三", "UTF-8"));
3. 转发后操作响应导致异常
问题现象 :控制台报IllegalStateException: Cannot call sendRedirect() after the response has been committed。
原因 :转发后服务器已开始向客户端发送响应,此时再调用response相关方法(如sendRedirect()、getWriter())会导致冲突。
解决方案 :转发后立即使用return终止当前Servlet的执行:
java
request.getRequestDispatcher("/views/success.jsp").forward(request, response);
return; // 关键:终止后续代码执行
4. 中文乱码问题
问题现象:传递的中文参数显示为乱码。
解决方案:
-
转发:在Servlet中设置请求编码:
javarequest.setCharacterEncoding("UTF-8"); -
重定向:URL参数需编码,目标组件解码:
java// 编码 String encodedName = URLEncoder.encode("张三", "UTF-8"); response.sendRedirect(contextPath + "/views/success.jsp?name=" + encodedName); // 解码 String name = URLDecoder.decode(request.getParameter("name"), "UTF-8");
六、最佳实践:如何选择转发与重定向?
在实际开发中,转发和重定向的选择应遵循"场景适配"原则:
优先使用转发的场景
- MVC模式中的视图渲染:Servlet(Controller)处理业务后,转发到JSP(View)展示数据(利用请求域传递模型数据)
- 同一请求的多步骤处理:如表单验证Servlet → 业务处理Servlet → 结果展示JSP(多组件协作完成同一请求)
- 需要隐藏目标资源路径:如不希望用户看到JSP的真实路径(转发可将JSP放在WEB-INF目录下,直接访问会404)
优先使用重定向的场景
- 表单提交后跳转:避免用户刷新页面导致表单重复提交(如注册成功后重定向到登录页)
- 权限验证后的跳转:如未登录用户访问受保护资源时,重定向到登录页
- 跨应用跳转:如从当前Web应用跳转到另一个应用或外部网站(如支付完成后跳回商户页面)
- 状态变化后的跳转:如用户注销登录后,重定向到首页(清除Session后需要全新请求)
七、总结:转发与重定向的核心心法
转发和重定向作为Servlet中两种基础的跳转机制,没有绝对的优劣之分,关键在于理解它们的底层原理和适用场景:
- 转发是"服务器内部的接力":一次请求,共享数据,适合同一业务流程的多组件协作
- 重定向是"客户端的二次访问":两次请求,数据隔离,适合不同业务流程的跳转
记住一个简单的判断准则:如果跳转前后属于同一业务请求 (如表单处理→结果展示),用转发;如果属于不同业务请求(如登录→首页),用重定向。
掌握转发与重定向的使用技巧,不仅能解决实际开发中的跳转问题,更能帮助你理解Web应用的请求处理流程,为后续学习框架(如Spring MVC的跳转机制)打下坚实基础。