目录
Web 应用在处理客户端请求时,经常需要多个 Web 资源共同协作才能生成响应结果。但由于 Servlet 对象无法直接调用其他 Servlet 的 service() 方法,所以 Servlet 规范提供了 2 种解决方案:
请求转发属于服务器行为。容器接收请求后,Servlet 会先对请求做一些预处理,然后将请求传递给其他 Web 资源,来完成包括生成响应在内的后续工作。
在 jakarta.servlet 包中定义了一个 RequestDispatcher 接口。继承关系如下图所示:
RequestDispatcher 对象由 Servlet 容器创建,用于封装由路径所标识的 Web 资源。利用 RequestDispatcher 对象可以把请求转发给其他的 Web 资源。
1)Servlet 可以通过以下2种方式获得 RequestDispatcher 对象:
绝对路径是指:以正斜杠 "/" 开头的路径,"/" 表示当前 Web 应用的根目录。
相对路径是指:相对当前 Web 资源的路径,不以正斜杠 "/" 开头。
2)RequestDispatcher 接口中提供了以下方法:
返回值类型 | 方法 | 描述 |
void | forward(ServletRequest request, ServletResponse response) | 用于将请求转发给另一个 Web 资源。该方法必须在响应给客户端之前被调用,否则将抛出 IllegalStateException 异常。 |
void | include(ServletRequest request, ServletResponse response) | 用于将其他的资源作为当前响应内容包含进来。 |
在 Servlet 中,通常使用 forward() 方法将当前请求转发给其他的 Web 资源进行处理。请求转发的工作原理如下图所示:
请求转发具有以下特点:
request 是 Servlet 的三大域对象之一,它需要与请求转发配合使用,才可以实现动态资源间的数据传递。在 ServletRequest 接口中定义了一系列操作属性的方法,如下表:
返回值类型 | 方法 | 描述 |
void | setAttribute(String name, Object o) | 将 Java 对象与属性名绑定,并将它作为一个属性存放到 request 对象中。参数 name 为属性名,参数 object 为属性值。 |
Object | getAttribute(String name) | 根据属性名 name,返回 request 中对应的属性值。 |
void | removeAttribute(String name) | 用于移除 request 对象中指定的属性。 |
Enumeration | getAttributeNames() | 用于返回 request 对象中的所有属性名的枚举集合。 |
创建一个名为 RequestDispatcherServletDemo 的类,代码如下:
- package com.hoperun.www;
-
- import java.io.IOException;
- import java.io.PrintWriter;
-
- import jakarta.servlet.ServletException;
- import jakarta.servlet.annotation.WebServlet;
- import jakarta.servlet.http.HttpServlet;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
-
- @WebServlet("/RequestDispatcherServlet")
- public class RequestDispatcherServletDemo extends HttpServlet {
-
- private static final long serialVersionUID = 1L;
-
- @Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
-
- // 设置向页面输出内容格式
- response.setContentType("text/html;charset=UTF-8");
-
- PrintWriter writer = response.getWriter();
-
- // 尝试在请求转发前,向 response 缓冲区写入内容,最后在页面查看是否展示.
- writer.write("
这是转发前响应信息里的内容。
"); -
- // 向 request 域对象中添加属性,传递给下一个 Web 资源.
- request.setAttribute("属性名1", "属性值1");
- request.setAttribute("属性名2", "属性值2");
- request.setAttribute("属性名3", "属性值3");
-
- // 转发
- request.getRequestDispatcher("/DoServlet").forward(request, response);
- }
-
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- doGet(req, resp);
- }
- }
然后,再创建一个名称为 DoServlet 的类,代码如下:
- package com.hoperun.www;
-
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.util.Arrays;
-
- import jakarta.servlet.ServletException;
- import jakarta.servlet.annotation.WebServlet;
- import jakarta.servlet.http.HttpServlet;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
-
- @WebServlet("/DoServlet")
- public class DoServletDemo extends HttpServlet {
-
- private static final long serialVersionUID = 1L;
-
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- // 设置向页面输出内容格式
- response.setContentType("text/html;charset=UTF-8");
- PrintWriter writer = response.getWriter();
-
- // 获取 request 域对象中的属性值
- String value1 = (String) request.getAttribute("属性名1");
- String value2 = (String) request.getAttribute("属性名2");
- String value3 = (String) request.getAttribute("属性名3");
- if (value1 != null) {
- writer.write("
"
+ value1 + ""); - }
- if (value2 != null) {
- writer.write("
"
+ value2 + ""); - }
- if (value3 != null) {
- writer.write("
"
+ value3 + ""); - }
- // 获取用户名
- String username = request.getParameter("username");
- // 获取密码
- String password = request.getParameter("password");
- // 获取性别
- String sex = request.getParameter("sex");
- // 获取城市
- String city = request.getParameter("city");
- // 获取使用语言
- String[] languages = request.getParameterValues("language");
- writer.write("用户名: " + username + "
" + "密码: " + password + "
" + "性别: " + sex + "
" + "城市: " + city - + "
" + "使用过的语言: " + Arrays.toString(languages)); - }
-
- protected void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- doGet(request, response);
- }
-
- }
在 webapp 根目录下,创建 index.html,代码如下:
- html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>Insert title heretitle>
- head>
- <body>
- <form action="/servletDemo/RequestDispatcherServlet" method="GET">
- <table border="1">
- <tr>
- <td colspan="2" align="center">CSDNtd>
- tr>
- <tr>
- <td>姓名td>
- <td>
- <input type="text" name="username" />
- td>
- tr>
- <tr>
- <td>密码td>
- <td>
- <input type="password" name="password" />
- td>
- tr>
- <tr>
- <td>性别td>
- <td>
- <input type="radio" name="sex" value="男" />男
- <input type="radio" name="sex" value="女" />女
- td>
- tr>
- <tr>
- <td>使用的语言td>
- <td>
- <input type="checkbox" name="language" value="JAVA" />JAVA
- <input type="checkbox" name="language" value="C语言" />C语言
- <input type="checkbox" name="language" value="PHP" />PHP
- <input type="checkbox" name="language" value="Python" />Python
- td>
- tr>
- <tr>
- <td>城市td>
- <td>
- <select name="city">
- <option value="none">--请选择--option>
- <option value="北京">北京option>
- <option value="上海">上海option>
- <option value="广州">广州option>
- select>
- td>
- tr>
- <tr>
- <td colspan="2" align="center" >
- <input type="submit" value="提交" />
- td>
- tr>
- table>
- form>
- body>
- html>
启动 Tomcat 服务器,在地址栏输入 http://localhost:8080/servletDemo/index.html,访问 index.html,结果如下图:
填写表单信息,点击提交,结果如下图:
重定向属于客户端行为。服务器在收到客户端请求后,会通知客户端浏览器重新向另外一个 URL 发送请求,这称为请求重定向。它本质上是两次 HTTP 请求,对应两个 request 对象和两个 response 对象。
重定向的工作流程如下:
HttpServletResponse 接口中的 sendRedirect() 方法用于实现重定向。
返回值类型 | 方法 | 描述 |
void | sendRedirect(String location) | 向浏览器返回状态码为 302 的响应结果,让浏览器访问新的 URL。若指定的 URL 是相对路径,Servlet 容器会将相对路径转换为绝对路径。参数 location 表示重定向的URL。 |
在 servletDemo 的 webapp 中,创建登录页面 login.html,代码如下:
- html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>Insert title heretitle>
- head>
- <body>
- <form action="/servletDemo/ReDirectServlet" method="GET">
- <table border="1">
- <tr>
- <td colspan="2" align="center">CSDNtd>
- tr>
- <tr>
- <td>姓名td>
- <td>
- <input type="text" name="username" />
- td>
- tr>
- <tr>
- <td>密码td>
- <td>
- <input type="password" name="password" />
- td>
- tr>
- <tr>
- <td>性别td>
- <td>
- <input type="radio" name="sex" value="男" />男
- <input type="radio" name="sex" value="女" />女
- td>
- tr>
- <tr>
- <td>使用的语言td>
- <td>
- <input type="checkbox" name="language" value="JAVA" />JAVA
- <input type="checkbox" name="language" value="C语言" />C语言
- <input type="checkbox" name="language" value="PHP" />PHP
- <input type="checkbox" name="language" value="Python" />Python
- td>
- tr>
- <tr>
- <td>城市td>
- <td>
- <select name="city">
- <option value="none">--请选择--option>
- <option value="北京">北京option>
- <option value="上海">上海option>
- <option value="广州">广州option>
- select>
- td>
- tr>
- <tr>
- <td>验证码td>
- <td><input type="text" name="code"/>
- <img id="imgId" src="/servletDemo/CheckCodeServlet" />
- <a href="#" onclick="run()">看不清,换一张a>
- td>
- tr>
- <tr>
- <td colspan="2" align="center" >
- <input type="submit" value="提交" />
- td>
- tr>
- table>
- form>
- body>
- <script type="text/javascript">
- // 看不清,换一张,时间戳
- function run() {
- // 获取图片
- var image = document.getElementById("imgId");
- image.src = "/servletDemo/CheckCodeServlet?" + new Date().getTime();
- }
- script>
- html>
创建名称为 CheckCodeServlet 的 Servlet 类,代码如下:
- package com.hoperun.www;
-
- import java.awt.Color;
- import java.awt.Font;
- import java.awt.Graphics2D;
- import java.awt.image.BufferedImage;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Random;
-
- import javax.imageio.ImageIO;
-
- import jakarta.servlet.ServletException;
- import jakarta.servlet.annotation.WebServlet;
- import jakarta.servlet.http.HttpServlet;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
-
- /**
- * 使用 Java 生成验证码图片,并通过 response 对象展示在页面上.
- */
- @WebServlet("/CheckCodeServlet")
- public class CheckCodeServlet extends HttpServlet {
- private static final long serialVersionUID = 1L;
-
- @Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- int width = 120;
- int height = 30;
- // 在内存中生成图片
- BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- // 先获取画笔对象
- Graphics2D g = (Graphics2D) image.getGraphics();
- // 设置灰色
- g.setColor(Color.GRAY);
- // 画填充的矩形
- g.fillRect(0, 0, width, height);
- // 设置颜色
- g.setColor(Color.BLUE);
- // 画边框
- g.drawRect(0, 0, width - 1, height - 1);
- // 设置颜色
- g.setColor(Color.YELLOW);
- // 设置字体
- g.setFont(new Font("隶书", Font.BOLD, 20));
- // 准备数据,随机获取4个字符
- String words = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- String code = "";
- // 构造存储字符串的集合
- List
list = new ArrayList(); - Random random = new Random();
- int x = 20;
- int y = 20;
- for (int i = 0; i < 4; i++) {
- // 获取正负30之间的角度
- int jiaodu = random.nextInt(60) - 30;
- double hudu = jiaodu * Math.PI / 180;
- g.rotate(hudu, x, y);
- // 获取下标
- int index = random.nextInt(words.length());
- // 返回指定下标位置的字符,随机获取下标
- char ch = words.charAt(index);
- // 将字符存入字符数组中
- char[] chc = { ch };
- // 使用字符数组构造字符串
- String string = new String(chc);
- // 将构造好的字符串添加进list集合中
- list.add(string);
- // 写字符串
- g.drawString("" + ch, x, y);
- g.rotate(-hudu, x, y);
- x += 20;
- }
- for (int i = 0; i < list.size(); i++) {
- code += list.get(i);
- }
- // 将验证码存入上下文中
- getServletContext().setAttribute("code", code);
- // 设置颜色
- g.setColor(Color.GREEN);
- int x1, x2, y1, y2;
- // 画干扰线
- for (int i = 0; i < 4; i++) {
- x1 = random.nextInt(width);
- y1 = random.nextInt(height);
- x2 = random.nextInt(width);
- y2 = random.nextInt(height);
- g.drawLine(x1, y1, x2, y2);
- }
- // 输出到客户端
- ImageIO.write(image, "jpg", response.getOutputStream());
- }
-
- @Override
- protected void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- doGet(request, response);
- }
-
- }
创建名称为 ReDirectServletDemo 的 Servlet 类,代码如下:
- package com.hoperun.www;
-
- import java.io.IOException;
-
- import jakarta.servlet.ServletException;
- import jakarta.servlet.annotation.WebServlet;
- import jakarta.servlet.http.HttpServlet;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
-
- /**
- * 验证提交的信息
- */
- @WebServlet("/ReDirectServlet")
- public class ReDirectServletDemo extends HttpServlet {
-
- private static final long serialVersionUID = 1L;
-
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- // 获取用户名
- String username = request.getParameter("username");
-
- // 获取密码
- String password = request.getParameter("password");
-
- // 获取验证码
- String code = request.getParameter("code");
-
- // 设置是否成功标识
- Boolean IsSuccess = true;
-
- // 从上下文获取存储的验证码
- String code1 = (String) getServletContext().getAttribute("code");
-
- // 账号密码为admin.且验证码(忽略大小写)输入正确,则跳转到登陆成功页面
- if (code != null && !code.isEmpty() && code.equalsIgnoreCase(code1) && "admin".equals(username)
- && "admin".equals(password)) {
- response.sendRedirect("/servletDemo/SuccessServlet");
- // 账号密码不为admin,设置错误信息
- } else if (!"admin".equals(username) || !"admin".equals(password)) {
- getServletContext().setAttribute("msg", "账号或密码不正确!");
- IsSuccess = false;
- // 验证码错误,设置错误信息
- } else if (code == null || code.isEmpty() || !code.equalsIgnoreCase(code1)) {
- getServletContext().setAttribute("msg", "验证码输入错误!");
- IsSuccess = false;
- }
-
- if (!IsSuccess) {
- // 设置自动跳转的时间,存储在上下文中
- getServletContext().setAttribute("time", 5);
-
- // 向request对象中设置属性requestAttr,在重定向之后取值。
- request.setAttribute("requestAttr", "重定向中使用request域对象传递的数据");
- response.sendRedirect("/servletDemo/RefreshServlet");
- }
- }
-
- protected void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- doGet(request, response);
- }
-
- }
创建名称为 RefreshServletDemo 的 Servlet 类,代码如下:
- package com.hoperun.www;
-
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.Date;
-
- import jakarta.servlet.ServletException;
- import jakarta.servlet.annotation.WebServlet;
- import jakarta.servlet.http.HttpServlet;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
-
- /**
- * 登录失败后,提示错误信息并定时跳转回登录页面. 通过设置响应头字段(refresh)实现页面的定时跳转
- */
- @WebServlet("/RefreshServlet")
- public class RefreshServletDemo extends HttpServlet {
-
- private static final long serialVersionUID = 1L;
-
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- Object requestAttr = request.getAttribute("requestAttr");
- // 获取存在上下文中的跳转时间
- int times = (int) getServletContext().getAttribute("time");
- // 获取存在上下文中的错误信息
- String msg = (String) getServletContext().getAttribute("msg");
- /**
- * 设置三个头信息,禁用浏览器缓存 Expires: -1 值是日期类型 Pragma : no-cache
- */
- // 设置三个头信息:
- // 设置禁用浏览器缓存
- response.setHeader("Cache-Control", "no-cache");
- response.setHeader("Pragma", "no-cache");
- // 这个头信息指定内容过期的时间,在这之后内容不再被缓存。-1:立即过期
- response.setDateHeader("Expires", -1);
- // 设置向页面输出的格式
- response.setContentType("text/html;charset=UTF-8");
- // 设置提示
- String title = msg + ",即将在" + times + "秒钟后跳转到登录页";
- // 使用默认时区和语言环境获得一个日历
- Calendar cale = Calendar.getInstance();
- // 将Calendar类型转换成Date类型
- Date tasktime = cale.getTime();
- // 设置日期输出的格式
- SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- // 格式化输出
- String nowTime = df.format(tasktime);
- // 只要倒计时时间没有到0,就直至输出下面内容
- if (times != 0) {
- response.getWriter()
- .write("\n" + "
" + title + "\n" + "\n" - + "
CSDN 提醒您:
" - + "
"
+ title + "\n" - + "
当前时间是: "
+ nowTime + "\n" - + "
重定向通过request传递的数据为: "
+ requestAttr + "\n"); - // 倒计时
- times--;
- // 将倒计时的时间重新存入上下文中覆盖原来的值
- getServletContext().setAttribute("time", times);
- // 通过refresh头完成页面刷新
- response.setHeader("refresh", "1;url=/servletDemo/RefreshServlet");
- } else {
- // 倒计时归零,则跳转到登陆页面
- response.sendRedirect("/servletDemo/login.html");
- }
- }
-
- protected void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- doGet(request, response);
- }
-
- }
创建名称为 SuccessServletDemo 的 Servlet 类,代码如下:
- package com.hoperun.www;
-
- import java.io.IOException;
- import java.io.PrintWriter;
-
- import jakarta.servlet.ServletException;
- import jakarta.servlet.annotation.WebServlet;
- import jakarta.servlet.http.HttpServlet;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
-
- /**
- * 登录成功
- */
- @WebServlet("/SuccessServlet")
- public class SuccessServletDemo extends HttpServlet {
-
- private static final long serialVersionUID = 1L;
-
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- // 设置响应输出的格式
- response.setContentType("text/html;charset=UTF-8");
- PrintWriter writer = response.getWriter();
- writer.write("
登录成功
"); - }
-
- protected void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- doGet(request, response);
- }
-
- }
启动 Tomcat,在地址栏输入http://localhost:8080/servletDemo/login.html,访问登录页,如下图所示:
在登录页面输入账号、密码、验证码等信息(当账号和密码都为 admin 时验证成功,否则验证失败),这里我们输入错误的验证码点击提交按钮,结果如下图:
输入正确信息,点击提交,结果如下图:
转发和重定向都能实现页面的跳转,两者区别如下:
区别 | 转发 | 重定向 |
浏览器地址栏 URL 是否发生改变 | 否 | 是 |
是否支持跨域跳转 | 否 | 是 |
请求与响应的次数 | 一次请求和一次响应 | 两次请求和两次响应 |
是否共享 request 对象和 response 对象 | 是 | 否 |
是否能通过 request 域对象传递数据 | 是 | 否 |
速度 | 相对要快 | 相对要慢 |
行为类型 | 服务器行为 | 客户端行为 |