Servlet程序中foward的實現原理是怎樣的?


高能!接下來我要寫一下如何用腦殘方式分析源代碼的方法(づ ̄ 3 ̄)づ

在沒看到這個Question的時候其實我不知道forword原理是怎樣的,只記得是伺服器轉發,還從來沒探究過原理細節,正好看看tomcat細節也不錯(雖然已經不用web容器了,但是要保持geek)

-----------

第一步:

寫個簡單的servlet,在你的forword處打個斷點。按流程一步步走

第二步:

開始分析,看內存參數變化,流程。走一套流程,應該比較完全,現在電腦沒有環境,我也懶得搞了,就在http://grepcode.com上面看看源碼試著走一下,我們先看一個方法,大家應該熟悉吧

public RequestDispatcher getRequestDispatcher(String path) {
//省去部分代碼
context.getMapper().map(uriMB, mappingData);
//省去部分代碼
Wrapper wrapper = (Wrapper) mappingData.wrapper;
String wrapperPath = mappingData.wrapperPath.toString();
String pathInfo = mappingData.pathInfo.toString();

mappingData.recycle();

// Construct a RequestDispatcher to process this request
return new ApplicationDispatcher
(wrapper, uriCC.toString(), wrapperPath, pathInfo,
queryString, null);
}

這個方法,根據路徑名「path」找到一個包含有Servlet的Wrapper,wrapper這個東西在後面非常重要哦,最後實例化一個ApplicationDispatcher,並且返回該ApplicationDispatcher。

首先我們進入下面這個包看一下,這個包非常重要哦。我們主要關注ApplicationDispatcher類

package org.apache.catalina.core;

ApplicationDispatcher實現了RequestDispather這個servlet規範介面,然後找到forward方法GC: ApplicationDispatcher

哎呀卧槽,好多亂七八糟的東西=。=!看不懂,我還是去看海賊王吧(開個玩笑).這個時候不要慌張,我們看上面的title

Forward this request and response to another resource for processing. Any runtime exception, IOException, or ServletException thrown by the called servlet will be propagated to the caller.

假設上面一段開發是做了些比較厲害的事吧,doFroward(req,resp)貌似才是正常的跳轉方法(應該沒人會懷疑吧)。那我們點進去吧,GC: ApplicationDispatcher doFroward方法太長

但是裡面注釋很多,通過我機智的猜想,我覺得processRequest才是比較核心的方法(所以是無腦的分析嘛(づ ̄ 3 ̄)づ,你們不信)

其實這個方法裡面幹了很多事情,但是和我們要探究的事情不在一條線上,我們主要關心在哪裡發起request,所以我們跟著request對象走就行了,看看有關的方法都做了寫啥,怎麼找到servelt的之類的,這個方法做了很多細節上的事情,包括

這樣一些額外的東西,好吧,我們進去processRequest看看GC: ApplicationDispatcher

╮(╯▽╰)╭,又TM沒注釋,(╯‵□′)╯︵┻━┻,看title吧

Prepare the request based on the filter configuration.

好吧,根據filter來做請求準備,然後大家又發現了invoke方法GC: ApplicationDispatcher,哇。聽這個方法名稱我想我們已經找到最終的地方了,方法有點長,我們一段一段的看看,先看title。英文不過關,用翻譯吧

Ask the resource represented by this RequestDispatcher to process the associated request, and create (or append to) the associated response.

然後我們看第一段代碼

%&>_&<%,看到這段的開始,我就預感到了,有啥?ClassLoader?HttpServletResponse ,Servlet介面 和必要的異常類,而且還有一句話,現在你可以放開你的思維!想想接下里會發生什麼

Initialize local variables we may need

然後我們的servlet出現的地方就是這裡,然後後面是調用,注意看注釋

Call the service() method for the allocated servlet instance

我們看看wrapper介面的allocate方法具體做了些啥?我們看看StandardWrapper GC: StandardWrapper類的allocate的實現,先直接看返回的地方在哪裡

=。=最後一行,表示實在一個池子裡面拿出來了,好吧,是不是對你的認識有一些同學有點不解,servlet咋會從池子里拿出來,servlet不是單例的嗎?

Allocate an initialized instance of this Servlet that is ready to have its service() method called. If the servlet class does not implement SingleThreadModel, the (only) initialized instance may be returned immediately. If the servlet class implementsSingleThreadModel, the Wrapper implementation must ensure that this instance is not allocated again until it is deallocated by a call to deallocate().

這裡有一個故事,不知道大家是否知道呢?:正因為servlet是單例的,所以會出現我們說的線程不安全,為了解決問題當時就設計了SingleThreadModel這個介面,維護一個servlet pool,所以你會看到這些東西,以下

-----

但是由於性能和不能解決所有問題,在API2.4以後Deprecated。

Ensures that servlets handle only one request at a time. This interface has no methods.

If a servlet implements this interface, you are guaranteed that no two threads will execute concurrently in the servlet"sservice method. The servlet container can make this guarantee by synchronizing access to a single instance of the servlet, or by maintaining a pool of servlet instances and dispatching each new request to a free servlet.

Note that SingleThreadModel does not solve all thread safety issues. For example, session attributes and static variables can still be accessed by multiple requests on multiple threads at the same time, even when SingleThreadModel servlets are used. It is recommended that a developer take other means to resolve those issues instead of implementing this interface, such as avoiding the usage of an instance variable or synchronizing the block of the code accessing those resources. This interface is deprecated in Servlet API version 2.4.

Deprecated:As of Java Servlet API 2.4, with no direct replacement.Author(s):Various

在allocate方法中,有一個initServlet方法,裡面會發現一些有趣的東西

其實很多細節都沒有講清楚(真的),碼字太累了!只能有個大概的分析輪廓,能看懂最好咯(づ ̄ 3 ̄)づ

引用:

深入研究Servlet線程安全性問題

Tomcat 系統架構與設計模式,第 1 部分: 工作原理


自己下載一個tomcat看代碼啊:我粘一段給你:

@Override
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);
}
}

private void doForward(ServletRequest request, ServletResponse response)
throws ServletException, IOException
{

// Reset any output that has been buffered, but keep headers/cookies
if (response.isCommitted()) {
throw new IllegalStateException
(sm.getString("applicationDispatcher.forward.ise"));
}
try {
response.resetBuffer();
} catch (IllegalStateException e) {
throw e;
}

// Set up to handle the specified request and response
State state = new State(request, response, false);

if (WRAP_SAME_OBJECT) {
// Check SRV.8.2 / SRV.14.2.5.1 compliance
checkSameObjects(request, response);
}

wrapResponse(state);
// Handle an HTTP named dispatcher forward
if ((servletPath == null) (pathInfo == null)) {

ApplicationHttpRequest wrequest =
(ApplicationHttpRequest) wrapRequest(state);
HttpServletRequest hrequest = state.hrequest;
wrequest.setRequestURI(hrequest.getRequestURI());
wrequest.setContextPath(hrequest.getContextPath());
wrequest.setServletPath(hrequest.getServletPath());
wrequest.setPathInfo(hrequest.getPathInfo());
wrequest.setQueryString(hrequest.getQueryString());

processRequest(request,response,state);
}

// Handle an HTTP path-based forward
else {

ApplicationHttpRequest wrequest =
(ApplicationHttpRequest) wrapRequest(state);
String contextPath = context.getPath();
HttpServletRequest hrequest = state.hrequest;
if (hrequest.getAttribute(
RequestDispatcher.FORWARD_REQUEST_URI) == null) {
wrequest.setAttribute(RequestDispatcher.FORWARD_REQUEST_URI,
hrequest.getRequestURI());
wrequest.setAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH,
hrequest.getContextPath());
wrequest.setAttribute(RequestDispatcher.FORWARD_SERVLET_PATH,
hrequest.getServletPath());
wrequest.setAttribute(RequestDispatcher.FORWARD_PATH_INFO,
hrequest.getPathInfo());
wrequest.setAttribute(RequestDispatcher.FORWARD_QUERY_STRING,
hrequest.getQueryString());
}

wrequest.setContextPath(contextPath);
wrequest.setRequestURI(requestURI);
wrequest.setServletPath(servletPath);
wrequest.setPathInfo(pathInfo);
if (queryString != null) {
wrequest.setQueryString(queryString);
wrequest.setQueryParams(queryString);
}

processRequest(request,response,state);
}

if (request.getAsyncContext() != null) {
// An async request was started during the forward, don"t close the
// response as it may be written to during the async handling
return;
}

// This is not a real close in order to support error processing
if (wrapper.getLogger().isDebugEnabled() )
wrapper.getLogger().debug(" Disabling the response for futher output");

if (response instanceof ResponseFacade) {
((ResponseFacade) response).finish();
} else {
// Servlet SRV.6.2.2. The Request/Response may have been wrapped
// and may no longer be instance of RequestFacade
if (wrapper.getLogger().isDebugEnabled()){
wrapper.getLogger().debug( " The Response is vehiculed using a wrapper: "
+ response.getClass().getName() );
}

// Close anyway
try {
PrintWriter writer = response.getWriter();
writer.close();
} catch (IllegalStateException e) {
try {
ServletOutputStream stream = response.getOutputStream();
stream.close();
} catch (IllegalStateException f) {
// Ignore
} catch (IOException f) {
// Ignore
}
} catch (IOException e) {
// Ignore
}
}

}


我想了想,如果是讓我去實現一個servlet容器的話,

forward方法大概是根據那個路徑找到新servlet,然後調用新servlet的service(req,res)方法吧。

-----------

題外話說一句redirect,redirect是返回給請求方一個302響應,有一個http頭Location是要重定向的url,然後請求方會重新發一個http請求。


我這裡有些相關的資料,希望對題主有所幫助!

在Servlet(JSP)中Forward與Redirect 的區別:

forward重定向是在容器內部實現的同一個Web應用程序的重定向,所以forward方法只能重定向到同一個Web應用程序中的一個資源,重定向後瀏覽器地址欄URL不變,而sendRedirect方法可以重定向到任何URL, 因為這種方法是修改http頭來實現的,URL沒什麼限制,重定向後瀏覽器地址欄URL改變。

forward重定向將原始的HTTP請求對象(request)從一個servlet實例傳遞到另一個實例,而採用sendRedirect方式兩者不是同一個application。

基於第二點,參數的傳遞方式不一樣。forward的form參數跟著傳遞,所以在第二個實例中可以取得HTTP請求的參數。sendRedirect只能通過鏈接傳遞參數,response.sendRedirect(「login.jsp?param1=a」)。

sendRedirect能夠處理相對URL,自動把它們轉換成絕對URL,如果地址是相對的,沒有一個『/』,那麼Web container就認為它是相對於當前的請求URI的。比如,如果為response.sendRedirect("login.jsp"),則會從當前servlet 的URL路徑下找login.jsp,重定向,如果為response.sendRedirect("/login.jsp")則會從當前應用徑下找 。

Servlet中的Forward與Redirect:

需要用到的類與方法有

ServletRequest.getRequestDispatcher(String)

RequestDispatcher.forward(request, response)

HttpServletResponse.sendRedirect(String)

Forward:

Java代碼

RequestDispatcher dispatcher = request.getRequestDispatcher("/a.jsp");

dispatcher .forward(request, response);

Java代碼

request.getNamedDispatche("ServletName").forward(request, response)

頁面的路徑是相對路徑。forward方式只能跳轉到本web應用中的頁面上。跳轉後瀏覽器地址欄不會變化。使用這種方式跳轉,傳值可以使用三種方法:url中帶parameter,session,request.setAttribute

Redirect:

Java代碼

response.sendRedirect;

response.sendRedirect;

頁面的路徑是相對路徑。sendRedirect可以將頁面跳轉到任何頁面,不一定局限於本web應用中。跳轉後瀏覽器地址欄變化。這種方式要傳值出去的話,只能在url中帶parameter或者放在session中,無法使用request.setAttribute來傳遞。

JSP中的forward跟redirect:

Redirect:

Java代碼

response.sendRedirect();

此語句前不允許有out.flush(),如果有,會有異常:

Java代碼

java.lang.IllegalStateException: Can"t sendRedirect() after data has committed to the client.

at com.caucho.server.connection.AbstractHttpResponse.sendRedirect

...

跳轉後瀏覽器地址欄變化。如果要跳到不同主機下,跳轉後,此語句後面的語句會繼續執行,如同新開了線程,但是對response的操作已經無意義了;如果要跳到相同主機下,此語句後面的語句執行完成後才會跳轉;

Forward:

Java代碼

&

此語句前不允許有out.flush(),如果有,會有異常:

Java代碼

java.lang.IllegalStateException: forward() not allowed after buffer has committed.

at com.caucho.server.webapp.RequestDispatcherImpl.forward

at com.caucho.server.webapp.RequestDispatcherImpl.forwardat com.caucho.jsp.PageContextImpl.forward

...

跳轉後瀏覽器地址欄不變,但是只能跳到當前主機下。此語句後面的語句執行完成後才會跳轉。


題主問原理,我簡單說一下:redirect 其實就是伺服器說這個請求我沒法處理,你找別人吧,對應的就是 http 302 和新的請求URL,你拿到這個信息後重新發起請求; forward 其實是伺服器說我不處理或者我只能處理一部分,但能處理的人是我家(同一個應用)的,我直接轉過去就給他處理了。同一個應用為啥能轉?你想,同一web容器了,裡面有啥難道容器會不知道?


forward就是轉發的意思,通俗點講就是瀏覽器發出請求,伺服器做出響應,在伺服器的內部進行地址的改變,你可以發現你的地址不是最終地址,而是轉發之前的網址,如果你用response跳轉網頁,就是重定向,相當於瀏覽器和伺服器一問一答,之前的數據不會保留。望採納


推薦閱讀:

電腦是如何知道1+1=2的?
互聯網行業哪個職位比較有前途?
IT達人都是自己組電腦嗎?大家自己都用什麼樣配置的
IBM有哪些黑科技?

TAG:編程 | 信息技術IT | Java | ApacheTomcat | Servlet |