Servlet请求转发

 
Web 应用在处理客户端的请求时,经常需要多个 Web 资源共同协作才能生成响应结果。但由于 Serlvet 对象无法直接调用其他 Servlet 的 service() 方法,所以 Servlet 规范提供了 2 种解决方案:
  1. 请求转发
  2. 请求包含(了解即可)

下面我们主要对请求转发进行介绍。

请求转发

请求转发属于服务器行为。容器接收请求后,Servlet 会先对请求做一些预处理,然后将请求传递给其他 Web 资源,来完成包括生成响应在内的后续工作。

RequestDispatcher 接口

javax.servlet 包中定义了一个 RequestDispatcher 接口,RequestDispatcher 对象由 Servlet 容器创建,用于封装由路径所标识的 Web 资源。利用 RequestDispatcher 对象可以把请求转发给其他的 Web 资源。

Servlet 可以通过 2 种方式获得 RequestDispatcher 对象:
  1. 调用 ServletContext 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,必须为绝对路径;
  2. 调用 ServletRequest 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,可以为绝对路径,也可以为相对路径。

绝对路径是指以符号“/”开头的路径,“/”表示当前 Web 应用的根目录。相对路径是指相对当前 Web 资源的路径,不以符号“/”开头。

RequestDispatcher 接口中提供了以下方法。

返回值类型 方法 功能描述
void forward(ServletRequest request,ServletResponse response)  用于将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常
 void include(ServletRequest request,ServletResponse response)  用于将其他的资源作为当前响应内容包含进来

请求转发的工作原理

在 Servlet 中,通常使用 forward() 方法将当前请求转发给其他的 Web 资源进行处理。请求转发的工作原理如下图所示。

请求转发流程图

请求转发的特点

请求转发具有以下特点:
  1. 请求转发不支持跨域访问,只能跳转到当前应用中的资源。
  2. 请求转发之后,浏览器地址栏中的 URL 不会发生变化,因此浏览器不知道在服务器内部发生了转发行为,更无法得知转发的次数。
  3. 参与请求转发的 Web 资源之间共享同一 request 对象和 response 对象。
  4. 由于 forward() 方法会先清空 response 缓冲区,因此只有转发到最后一个 Web 资源时,生成的响应才会被发送到客户端。

request 域对象

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 对象中的所有属性名的枚举集合。 

Context 域对象和 request 域对象对比,具有以下 4 点差异:

1) 生命周期不同

Context 域对象的生命周期从容器启动开始,到容器关闭或者 Web 应用被移除时结束;

request 域对象的生命周期从客户端向容器发送请求开始,到对这次请求做出响应后结束。

2) 作用域不同

Context 域对象对整个 Web 应用内的所有Servlet都有效;

request 域对象只对本次请求涉及的 Servlet 有效。

3) Web 应用中数量不同

整个 Web 应用中只有一个 Context 域对象;

由于 Servlet 能处理多个请求,因此 Web 应用中的每个 Servlet 实例都可以有多个 request 域对象。

4) 实现数据共享的方式不同

Context 域对象可以独立完成动态资源之间的数据共享;

Request 域对象需要与请求转发配合使用才能实现动态资源之间的数据共享。

示例

下面我们通过一个案例加深大家对转发和 request 域对象的理解。

在 httpServletRequestDemo 的 net.biancheng.www 包中,创建一个名为 DispatcherServlet 的类,代码如下。
package net.biancheng.www;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮  www.biancheng.net
*请求转发
*/
@WebServlet("/DispatcherServlet")
public class DispatcherServlet 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();
        // 尝试在请求转发前向response缓冲区写入内容,最后在页面查看是否展示
        writer.write("<h1>这是转发前在响应信息内的内容!</h1>");
        // 向reuqest域对象中添加属性,传递给下一个web资源
        request.setAttribute("webName", "C语言中文网");
        request.setAttribute("url", "www.biancheng.net");
        request.setAttribute("welcome", "C语言中文网,欢迎你");
        // 转发
        request.getRequestDispatcher("/DoServlet").forward(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

然后,再创建一个名称为 DoServlet 的类,代码如下。
package net.biancheng.www;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
*
* @author 编程帮 www.biancheng.net
* 请求转发
*
*/
@WebServlet("/DoServlet")
public class DoServlet 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();
        String webName = (String) request.getAttribute("webName");
        String url = (String) request.getAttribute("url");
        String welcome = (String) request.getAttribute("welcome");
        if (webName != null) {
            writer.write("<h3>" + webName + "</h3>");
        }
        if (url != null) {
            writer.write("<h3>" + url + "</h3>");
        }
        if (welcome != null) {
            writer.write("<h3>" + welcome + "</h3>");
        }
        String username = request.getParameter("username");
        // 获取密码
        String password = request.getParameter("password");
        // 获取性别
        String sex = request.getParameter("sex");
        // 获取城市
        String city = request.getParameter("city");
        // 获取使用语言返回是String数组
        String[] languages = request.getParameterValues("language");
        writer.write("用户名:" + username + "<br/>" + "密码:" + password + "<br/>" + "性别:" + sex + "<br/>" + "城市:" + city
                + "<br/>" + "使用过的语言:" + Arrays.toString(languages) + "<br/>"
        );
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

在 WebContent 根目录下,创建 login.html,代码如下。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form action="/httpServletRequestDemo/DispatcherServlet" method="GET">
        <table border="1" width="50%">
            <tr>
                <td colspan="2" align="center">编程帮wwww.biancheng.net</td>
            </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/httpServletRequestDemo/login.html”,访问 login.html,结果如下图。

请求转发login

填写表单信息,点击提交,结果如下图。