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對象的保持和轉發。