1. 概述
在J2EE开发中,我们总是会接触到请求重定向和请求转发
重定向是指通过各种方法将网络请求重新定个方向转到其它位置,而转发是指把网页的请求重新转发到另一个站点。那么请求转发和请求重定向有什么区别,以及其实现的原理是怎么样的呢。
2. 两者的区别
(1)请求重定向是一种客户端行为,通过HttpServletResponse的对象来实现
response.sendRedirect();
HttpServletResponse.sendRedirect方法调用者与被调用者使用各自的request对象和response对象,它们属于两个独立的访问请求和响应过程。重定向不会保留上一次的请求对象,request域中的数据不能保留,地址栏的URL地址会改变。HttpServletResponse.sendRedirect方法对浏览器的请求直接作出响应,响应的结果就是告诉浏览器去重新发出对另外一个URL的访问请求。
请求重定向的过程:
客户浏览器发送http请求—-》web服务器接受后发送302状态码响应及对应新的location给客户浏览器–》客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址—-》服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器路径栏显示的是其重定向的路径,客户可以观察到地址的变化的。重定向行为是浏览器做了至少两次的访问请求的。
(2)请求转发是一种服务器行为,通过HttpServletRequest对象获取RequestDispatcher来实现。
request.getRequsetDispatcher().forward(requset,response);
RequestDispatcher.forward方法的调用者与被调用者之间共享相同的request对象和response对象,它们属于同一个访问请求和响应过程;两次访问前后只是一次请求,转发后请求对象会保存,request中的数据会保留,地址栏的URL地址不会改变。(服务器内部转发,所有客户端看不到地址栏的改变)。RequestDispatcher.forward方法在服务器端内部将请求转发给另外一个资源,浏览器只知道发出了请求并得到了响应结果,并不知道在服务器程序内部发生了转发行为。
请求转发的过程:
客户浏览器发送http请求—-》web服务器接受此请求–》调用内部的一个方法在容器内部完成请求处理和转发动作—-》将目标资源发送给客户;在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。
3. 实现原理
下面我们一起来看一下请求转发和请求重定向的实现原理。我们可以发现HttpServletRequest和HttpServletResponse是两个接口,而且我们在javaee的文档中并不能找到他们的实现类,那么他们到底是怎么实现的呢。
趋势,request和response只是规范中的一个名称而已。不是SUN提供的,这是由各个不同的Servlet提供商编写的,SUN只是规定这个类要实现HttpServletRequest和HttpServletResponse接口,并且规定了各个方法的用途,但具体是什么类是由各个提供商自己决定的。下面我们就来看一下HttpServletResponse.sendRedirect和RequestDispatcher.forward这两个方法的实现。
例如我使用的是tomcat服务器,那么这两个接口的实现就是由tomcat来完成的。
那么我们如何找到他们的实现类呢。我们可以使用下面的代码来直接输出实现类的名称。
System.out.println(response);
System.out.println(request.getRequestDispatcher("/login.jsp"));
输出结果如下:
org.apache.catalina.connector.ResponseFacade@1747c
org.apache.catalina.core.ApplicationDispatcher@1143c2b6
通过查找tomcat的源码我们可以进一步找到ResponseFacade和ApplicationDispatcher这两个类的源码。
首先分析一下ResponseFacade
/**
* Facade class that wraps a Coyote response object.
* All methods are delegated to the wrapped response.
*
* @author Remy Maucherat
* @author Jean-Francois Arcand
* @version $Id: ResponseFacade.java 939336 2010-04-29 15:00:41Z kkolinko
*/
文档注释指出,ResponseFacade是Response的一个外观类而已,方法的实现由Response实现。我们进一步看一下Response的实现,我们可以找到sendRedirect方法。
/**
* Send a temporary redirect to the specified redirect location URL.
*
* @param location Location URL to redirect to
*
* @exception IllegalStateException if this response has
* already been committed
* @exception IOException if an input/output error occurs
*/
public void sendRedirect(String location)
throws IOException {
if (isCommitted())
throw new IllegalStateException
(sm.getString("coyoteResponse.sendRedirect.ise"));
// Ignore any call from an included servlet
if (included)
return;
// Clear any data content that has been buffered
resetBuffer();
// Generate a temporary redirect to the specified location
try {
String absolute = toAbsolute(location);
setStatus(SC_FOUND);
setHeader("Location", absolute);
} catch (IllegalArgumentException e) {
setStatus(SC_NOT_FOUND);
}
// Cause the response to be finished (from the application perspective)
setSuspended(true);
}
我们可以大致推测出这个方法是通过重新设置http协议的location来实现重定向的,至于底层如何实现的,有兴趣可以继续深入。
下面我们来看一下,ApplicationDispatcher的forward方法。
* Forward this request and response to another resource for processing.
* Any runtime exception, IOException, or ServletException thrown by the
* called servlet will be propogated to the caller.
*
* @param request The servlet request to be forwarded
* @param response The servlet response to be forwarded
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet exception occurs
*/
public void forward(ServletRequest request, ServletResponse response)
throws ServletException, IOException
{
if (Globals.IS_SECURITY_ENABLED) {
try {
PrivilegedForward dp = new PrivilegedForward(request,response);
AccessController.doPrivileged(dp);
} catch (PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException)
throw (ServletException) e;
throw (IOException) e;
}
} else {
doForward(request,response);
}
}
这个类内部定义了一个保护内部类,PrivilegedForward,用来实现请request和response对象的保持和转发。