1. 程式人生 > >在jsp中的request

在jsp中的request

      今天在知乎看到一個小問題,可能這也是前期我們都有疑問的,答主的解答比較好,於是就乾脆收錄過來,自己消化,別方便大家理解。

原問題:

在jsp介面用request.setAttribute("aaa","bbb")提交到servlet後,在servlet中用
request.getAttribute("aaa");語句卻為什麼接受不到任何值啊!

解答(以下附上了作者資訊): 作者:chainho
連結:https://www.zhihu.com/question/39518170/answer/81742503
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

首先先上關於setAttribute這個方法的javadoc
/**
 * Stores an attribute in this request. Attributes are reset between
 * requests. This method is most often used in conjunction with
 * {@link RequestDispatcher}.
 * <p>
 * Attribute names should follow the same conventions as package names.
 * Names beginning with <code>java.*</code> and <code>javax.*</code> are
 * reserved for use by the Servlet specification. Names beginning with
 * <code>sun.*</code>, <code>com.sun.*</code>, <code>oracle.*</code> and
 * <code>com.oracle.*</code>) are reserved for use by Oracle Corporation.
 * <br>
 * If the object passed in is null, the effect is the same as calling
 * {@link #removeAttribute}. <br>
 * It is warned that when the request is dispatched from the servlet resides
 * in a different web application by <code>RequestDispatcher</code>, the
 * object set by this method may not be correctly retrieved in the caller
 * servlet.
 *
 * @param name
 *            a <code>String</code> specifying the name of the attribute
 * @param o
 *            the <code>Object</code> to be stored
 */

注意這一句:
Attributes are reset between
 * requests. This method is most often used in conjunction with
 * {@link RequestDispatcher}.
主要是用在重定向這塊的。
而為什麼當你提交之後就不能再取到值呢?這是因為你在提交之後,這個setAttribute是在jsp頁面渲染的時候已經設定,這個請求響應完成之後,對於requet和response都會執行一個這樣的動作
request.recycle();
response.recycle();

這個recycle會將之前的設定清空的。

又深入原始碼分析了下重定向和轉發兩者的實現方式,來從本質上說明為什麼這些資料為什麼不能被保留下來。

說起重定向,轉發,每個Web開發者估計都能頭頭是道的說上幾句類似於重定向會顯示真實路徑,轉發不會以及其它一類教科書式的概念。這些概念也都沒錯,但卻沒從本質上說明這兩者的區別。更別提其根本原理是怎麼實現的,以及為何重定向之後,原來的request中的資料都丟掉了,而轉發卻還是能保證request中的資料依然保留呢?



作為一個有追求的程式設計師,希望本文可以讓你深入瞭解這兩者的本質區別,從而知其所以然。


  1. 定義


首先來看兩者的javadoc。


sendRedirect()

/**
* Sends a temporary redirect response to the client using the 
specified redirect location URL. This method can accept relative URLs; the servlet container must convert the relative URL to an absolute URL before sending the response to the client. If the location is relative without a leading '/' the container interprets it as relative to the current request URI.
If the location is relative with a leading '/' the container interprets it as relative to the servlet container root.
*/
public void sendRedirect(String location) throws IOException;

重定向是向客戶端傳送一個指定URL的臨時重定向的響應。


forward()

/**
* Forwards a request from a servlet to another resource (servlet, JSP file, or HTML file) on the server. This method allows one servlet to do preliminary processing of a request and another resource to generate the response.
The request and response parameters must be either the same objects as were passed to the calling servlet's service method .
*/
public void forward(ServletRequest request, ServletResponse response);

轉發,則是將一個請求轉到伺服器的另一個資源。在處理完初步請求另外的資源之後生成響應。



定義基本說明了轉發操作為何可以保持request內的parameter,attribute這些值都可以保留,而重定向操作卻會丟棄的原因:


  • 轉發是在服務端完成的,並沒有經過客戶端

  • 轉發整個操作完成後才生成響應

  • 重定向是服務端向客戶端傳送指定的URL

  • 重定向是在客戶端完成的


我們再來看Tomcat內部,對於兩者是怎樣一種實現方式。


2. 容器實現


我們在servlet內部一般對於這兩者的使用形式也相當直觀,例如對於hello.jsp的請求:


sendRedirct方法

response.sendRedirect("/hello.jsp");
此時,內部的處理方式如下:
public void sendRedirect(String location, int status) throws IOException {
// Generate a temporary redirect to the specified location
try {
        String absolute = toAbsolute(location);
        setStatus(status); //這裡,重定向是返回302狀態碼以及Location和對應的url
setHeader("Location", absolute);
    } catch (IllegalArgumentException e) {
        setStatus(SC_NOT_FOUND);
    }}

展現在瀏覽器中的結果如下:
&amp;lt;img src=&quot;https://pic3.zhimg.com/50/83b51cd54b22b4c6342f18a5c99ade7c_hd.jpg&quot; data-rawwidth=&quot;403&quot; data-rawheight=&quot;169&quot; class=&quot;content_image&quot; width=&quot;403&quot;&amp;gt;

即根據Location,瀏覽器最終再發起新的請求,最終展現在瀏覽器中的即為新請求的URL,也就是大家常說的重定向會顯示最終的URL。


有上這些並不能造成重定向操作將之前request中已經繫結的一系列parameter和attribute丟掉。最根本的原因是一個請求完整處理完成之後,整個請求會有一個release的過程,即CoyoteAdapter的service方法執行完的finally塊中執行release這一過程,基本如下:
finally {
     if (!comet && !async || error.get()) {
request.recycle();  //注意這兩行程式碼
response.recycle();
    } 
}
具體request的recycle部分程式碼如下:
/**
 * Release all object references, and initialize instance variables, in preparation for reuse of this object.
 */
public void recycle() {
attributes.clear();
requestedSessionId = null;
requestedSessionURL = false;
parameterMap.clear();
pathParameters.clear();
}
我們看到用於儲存setAttribute方法設定的和setParameter方法設定的資料在這裡都clear掉了。這也是重定向不能夠保留資料的真正原因。


forward方法


forward方法一般使用如下:
request.getRequestDispatcher("/hello.jsp").forward(request, response);
forward方法內部最終會呼叫dispatcher的doForward方法
void doForward(ServletRequest request, ServletResponse response){
// Set up to handle the specified request and response
    State state = new State(request, response, false);
    wrapResponse(state);
        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_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); //進行第二個資源的請求
    }
}

第二個資源的請求處理與一般的請求處理類似,只是在第一個請求之上,並沒有返回響應時繼續發起第二個請求,此時第一個請求的各類引數會繼續向後傳遞,最終資料全部處理完成之後,整個響應傳送回客戶端。此時上面的release流程也依然會走,但並沒有什麼影響,畢竟第二個資源已經請求處理完成。

而由於瀏覽器發請求的時候是一個固定的URL,整個重定向是服務端內部進行的,瀏覽器並沒有感知到,因此也不會顯示出來。



整個應用伺服器內部處理過程再加上清晰的定義,相信這兩者的本質區別已經顯露無疑。