请求转发和请求包含原理细解

本文详细解释了Servlet容器的工作原理及请求转发与请求包含的区别,包括实现方式、处理流程及应用场景,帮助读者深入理解Servlet机制。

1、Servlet 容器

 编程中的容器我们可以理解为程序运行时需要的环境,那么Tomcat 就是Servlet 的运行环境,就是一个Servlet 容器。Servlet 容器的作用是负责处理客户请求,当Servlet 容器获取到用户请求后,调用某个Servlet,并把Servlet 的执行结果返回给用户。

 Servlet 容器的工作流程:

  ● 当用户请求某个资源时,Servlet 容器使用ServletRequest 对象将用户的请求信息封装起来,然后调用 java Servlet API 中定义的Servlet 的生命周期方法,完成Servlet 的运行。

  ● Servlet 容器将Servlet 执行后需要返回用户的结果封装到 ServletResponse 对象中,最后由Servlet 容器发送给客户,完成对客户的一次服务过程。

  ● 每一个Servlet 都会执行 init()、service()、destory() 三个方法,在启动时调用一次init) 方法对参数进行初始化,在该Servlet 生存期间每当收到对其的请求时都会调用Service() 方法对请求进行处理,当容器销毁时自动调用 destory() 方法对Servlet 进行销毁。

2、请求转发和请求包含

  正是因为因为Servlet 中的service() 方法由Servlet 容器调用,所以一个 Servlet 的对象是无法调用另一个 Servlet 的方法的,但是在实际项目中,对于客户端请求做出的响应可能会复杂,需要多个Servlet 来协作完成,这就需要请求转发和请求包含技术了。但是,要注意,无论是请求转发还是请求包含,都是表示由多个Servlet 共同处理同一个请求。

(1)请求转发定义:

  Servlet(源组件)先对客户请求做一些预处理操作(一般是对响应头进行处理),然后把请求转发给其他Servlet(目标组件)来完成包括生成响应结果在内的后续操作。

  实现方法:request.getRequestDispatcher(“接收请求的Servlet 路径”). forward(request,response)

  getRequestDispatcher(String path):该方法的返回值类型是RequestDispatcher,请求发送器,该方法的参数是指明要接收请求的Servlet 的路径;

  forward(ServletRequest req,ServletResponse res):该方法是RequestDispatcher 接口的方法,将请求从一个 servlet 转发到服务器上的另一个资源(servlet、JSP 文件或 HTML 文件)。此方法允许一个 servlet 对请求进行初步处理,并使另一个资源生成响应。需要传递两个参数,这两个参数是当前Servlet 的request 对象和 response 对象传递过去的。

  forward() 方法的处理流程:

  ● 清空用于存放响应正文(响应体)数据的缓冲区。

  ● 如果目标组件为Servlet 或JSP,就调用它们的service() 方法,把该方法产生的响应结果发送到客户端,如果目标组件为文件系统中的静态 html 文档,就读去文档中的数据并把它发送到客户端。

  ● 由于 forward() 方法先清空用于存放响应正文数据的缓冲区,因此servlet源组件生成的响应结果不会被发送到客户端,只有目标组件生成的结果才会被发送到客户端,所以对源组件叫“留头不留体”,目标组件为“留体不留头”。

  ● 如果源组件在进行请求转发之前,已经提交了响应结果(例如调用了flush 或close() 方法),那么forward() 方法会抛出IllegalStateException。为了避免该异常,不应该在源组件中提交响应结果,所以叫留体抛异常。

(2)请求包含定义:

  Servlet(源组件)把其他Servlet(目标组件)生成的响应结果包含到自身的响应结果中。

  实现方式:request.getRequestDispatcher(“接收请求的Servlet 路径”). include(request,response)

  include(ServletRequest request,ServletResponse response):该方法是RequestDispatcher 接口的方法,表示包含。它的参数同forward() 方法的参数一样都是由当前Servlet传递过去的。

  包含与转发相比,源组件与被包含的目标组件的输出数据都会被添加到响应结果中,在目标组件中对响应状态代码或者响应头所做的修改都会被忽略,所以对源组件来说是“留头又留体”,对目标组件为“留体不留头”。

  注意:当Servlet 源组件调用 RequestDispatcher 的 forward 或 include 方法时,都要把当前的 ServletRequest 对象和ServletResponse 对象作为参数传给 forward 或 include 方法,这就使得源组件和目标组件共享同一个ServletRequest 对象和ServletResponse 对象,就实现了多个Servlet 协同处理同一个请求。

附:RequestDispatcher 接口

  RequestDispatcher 接口中定义了两个方法::forward() 方法和 include() 方法,它们分别用于将请求转发到 RequestDispatcher 对象封装的资源和将 RequestDispatcher 对象封装的资源作为当前响应内容的一部分包含进来.

3、请求转发(留头不留体,留体抛异常)

  AServlet(发送请求方):


package web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class AServlet extends HttpServlet {
 
     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         
         response.setContentType( "text/html;charset=utf-8" );
         response.getWriter().print( "您好!" );
         //response.getWriter().flush();//刷新会导致response的状态为已提交!
         
         // 转发不能在response提交之后,否则就会抛异常
         request.getRequestDispatcher( "/BServlet" ).forward(request, response);

BServlet(接收请求方):

package web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class BServlet extends HttpServlet {
     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {      
         response.getWriter().print( "我很棒!" );
     }
}

 运行结果为:“我很棒!”(不会输出“你好!”)。

  我对留头不留体的分析:

    对于发出请求的Aservlet是:留头不留体(设置的响应头可以留下,响应体被接收请求的 Bservlet 响应体覆盖); 留体抛异常,就是说只要请求转发了,就要将请求完全由 Bservlet 处理,Aservlet就不能插手了,如果Aservlet 也提交了对请求的处理,那么Bservlet 就不能处理请求了(因为请求已经被Aservlet 处理了,还处理啥) ,你安排人家处理请求,最终却由你处理了,当forward() 转发请求时,发现请求已经被处理,就会抛出异常。

    不过要注意,只有当AServlet 提交了处理(如例中手动flush将缓冲区内数据提交)才会抛出异常,如果没有提交,说明之前A对请求的处理还在缓冲区中,B就会直接将A的缓冲区清空,然后覆盖掉,就形成了之前的留头不留体。


4、请求包含(留头又留体)

 CServlet(发送请求方):

package web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
/**
  * 请求包含:留头又留体
  */
public class CServlet extends HttpServlet {
 
     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         
         response.setContentType( "text/html;charset=utf-8" ); //设置内容类型
         response.getWriter().print( "你好!" );
         request.getRequestDispatcher( "/DServlet" ).include(request, response);
     }
}

  DServlet(接收请求方):

package web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class DServlet extends HttpServlet {
     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {     
         response.getWriter().print( "我很棒!" );
     }
}

 运行结果为:你好!我很棒!

  结果说明请求包含是多个Servlet 共同处理一个请求的,并且发送方和接收方都能够留下响应体。

5、请求转发和请求包含的比较

  (1)相同点:

    请求转发和请求包含都是在处理一个相同的请求,多个Servlet之间使用同一个 request 对象和 response 对象。

  (2)不同点:

    ● 如果在AServlet中请求转发到BServlet,那么在AServlet中不允许再输出响应体,即不能使用response.getWriter() 和response.getOutputStream() 向客户端输出,这一工作交由BServlet来完成;如果是由AServlet请求包含BServlet,则没有这个限制。

    ● 请求转发不能设置响应体,但是可以设置响应头,简单来说就是“留头不留体”,例如:response.setContentType("text/html;charset=utf-8”) 是可以留下来的;请求包含不仅可以设置响应头,还可以设置响应体,简单来说就是“留头又留体“。

    ● 请求转发大多应用在Servlet中,转发目标大多是jsp页面;请求包含大多应用在jsp页面中,完成多页面的合并。一般情况下经常使用的是请求转发。

6、请求转发的应用:

  ● 在Servlet中向数据库获取数据,保存到request域中;

  ● 转发到jsp页面,jsp从request域中获取数据,显示在页面上。

7、请求转发和重定向的区别

  ● 对于客户端浏览器来说,转发是一个请求,重定向是两个请求;

  ● 转发浏览器地址栏不变化,重定向会变成转发后的URL ;

  ● 转发只能在一个项目内,而重定向没有限制,可以重定向到任意网址,如京东、淘宝等 ;

  ● 转发可以使用request 域传递数据,而重定向不能。因为转发是一个请求,重定向是两个请求;

  ● 转发只有一个请求,原来是什么请求方式就是什么方式;而重定向两个请求,第一个可能为post 可能为get ,但是第二个请求一定是get 。

小结:在浏览器地址栏中输入某个URL地址或者单击网页上的一个超链接时,浏览器发出的HTTP请求消息的请求方式为GET如果网页当中的

表单元素的method属性被设置为“GET”,浏览器提交这个FORM表单时生成的HTTP请求消息的请求方式也为GET。



<think>好的,我现在需要回答用户关于ServletJSP请求转发请求包含的区别的问题。首先,我得回忆一下这两个概念的基本定义工作原理。 根据引用中的资料,请求转发(Request Forwarding)是服务器内部将请求从一个资源转到另一个资源,客户端只发送一次请求,URL不变。比如在Servlet中使用RequestDispatcher的forward方法。而请求包含(Request Including)可能也是类似的机制,但具体区别在哪里呢? 我记得请求包含允许将多个资源的输出合并到同一个响应中,而请求转发则是完全将控制权交给另一个资源,之后不再回到原来的资源。例如,转发后原Servlet的输出会被清空,而包含则是将目标资源的输出包含进来,原Servlet还可以继续输出内容。这点可能需要验证。 接下来,我需要查阅相关引用。引用[3]提到请求转发是服务器内部处理,客户端URL不变。引用[4]提到转发可以访问/WEB-INF/下的资源,而重定向不行。请求包含应该也是类似转发,但处理方式不同。比如,使用include()方法而不是forward()。 关于两者的区别,可能有以下几点:1)控制权是否返回原资源,转发后不再回来,包含会回来继续处理;2)响应内容的合并方式,包含会合并多个输出,转发只使用目标资源的输出;3)对响应头的影响,转发前不能提交头,包含可能允许添加内容。 需要确认这些点是否正确。例如,请求转发时,一旦调用forward(),后续的代码不会执行,而include()则会在包含目标资源后继续执行原Servlet的代码。同时,转发时响应缓冲区会被清空,而包含则不会,这样原Servlet可以继续写入响应。 此外,引用[5]提到转发是服务器内部跳转,客户端不知情,而包含可能也是类似。但包含允许组合多个资源的内容,比如页面的头部尾部。 现在需要将这些信息组织成结构清晰的回答,确保符合用户的要求,使用中文,正确引用来源,并生成相关问题。</think>### 请求转发 vs 请求包含 区别(Servlet/JSP) #### 1. **定义与核心区别** - **请求转发(Forward)** 通过`RequestDispatcher.forward()`实现,将请求**完全移交**给目标资源处理,原资源不再参与响应输出。转发后,原Servlet的响应缓冲区会被清空,最终客户端看到的响应内容仅来自目标资源[^3][^5]。 - **请求包含(Include)** 通过`RequestDispatcher.include()`实现,将目标资源的响应内容**合并到原资源的响应**中。原资源可继续处理逻辑并输出内容,最终响应是两者的组合[^3]。 #### 2. **执行流程对比** | 特性 | 请求转发 | 请求包含 | |---------------------|----------------------------|----------------------------| | **控制权** | 移交目标资源,原资源终止执行 | 目标资源执行后,返回原资源继续执行 | | **响应内容** | 仅目标资源的输出 | 原资源 + 目标资源的合并输出 | | **响应头修改权限** | 必须在转发前设置响应头 | 允许在包含后修改响应头 | | **典型应用场景** | 页面跳转、权限校验后转发 | 页面模块化(如包含页头/页脚) | #### 3. **代码示例** ```java // 请求转发示例 RequestDispatcher forwardDispatcher = request.getRequestDispatcher("/target.jsp"); forwardDispatcher.forward(request, response); // 后续代码不会执行 // 请求包含示例 response.getWriter().write("原资源内容 - "); RequestDispatcher includeDispatcher = request.getRequestDispatcher("/header.jsp"); includeDispatcher.include(request, response); // 包含header.jsp的内容 response.getWriter().write(" - 继续输出"); ``` #### 4. **关键注意事项** - **路径规则** 两者均使用**服务器端路径**,以`/`开头表示当前Web应用根目录(如`/WEB-INF/login.jsp`),支持访问受保护资源(如`/WEB-INF/`目录下的文件)[^2][^4]。 - **响应提交限制** 转发前若已调用`response.getWriter()`写入内容或提交响应(如自动刷新),会抛出`IllegalStateException`[^5]。 #### 5. **选择建议** - 需要**完全跳转**且不保留原资源内容时,使用**请求转发**。 - 需要**组合多个资源输出**(如模板页面)时,使用**请求包含**。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值