1. 程式人生 > >Java Web基礎知識之Servlet(3):Session管理

Java Web基礎知識之Servlet(3):Session管理

Session 管理是Web應用開發中的一個重要的內容,其實每天我們瀏覽網站,網站的後臺都是通過這門技術來記錄我們的瀏覽狀態,最典型的就是登入,每次你在網站上登入一次,當跳轉到該網站的任何其他頁面都不會再次要求你登入,這就是使用了Session管理技術。那麼問題來了我們為什麼需要這門技術?這是因為Http協議是無狀態的,什麼是無狀態?簡單來說就是Web伺服器不能區分請求是否來自一個人,也就是說每個使用者傳送的http請求是獨立的,彼此之間沒有聯絡的,所以說伺服器不知道請求是來自初次使用者還是之前已經訪問過的使用者。但是現實情況又有這種需要記住是誰發的請求的需求,所以說我們就要提出一些具體的技術來解決這個問題,就是對我們的session進行追蹤。

對Session 進行追蹤主要有4種技術:

一、 網址重寫

網址重寫怎麼做?是通過在將要訪問的URL後加上一個類似查詢字串的標識,以此來區分是是哪個請求。比如我們要訪問的URL是這樣的:
http://localhost:8080/JavaServlet/testServlet
但是你可在前臺通過一個連結將URL改寫成別的,例如使用<a>元素,如下:
<a href='?name=lmy'>點我</a>
任何相對的URL都會被當成相對於當前頁面的URL,那麼現在要訪問的URL就變成了如下:
http://localhost:8080/JavaServlet/testServlet?name=lmy
這樣就改寫了要訪問的URL,但是這種方法有如下幾個缺點:
  • 不安全:最明顯的就是全是查詢字串明文,簡直是裸奔嘛!!
  • 數量太多:點選的每個連結都要加上href後的查詢字串,想想某寶的一個頁面,多到數不清!如果想跨越多個頁面時處理起來就比較麻煩了。
  • 靜態頁面麻煩:要實現加入連結必須在伺服器端實現,也就是對動態頁面支援比較好,但是靜態頁面就比較費勁了!
  • 字元限制:有的瀏覽器對URL的字元數量是有限制的;

二、 隱藏域

利用隱藏域來保持狀態類似於網址重寫,但是不會加入到URL後面,而是將它們放到HTML表單的隱藏域中,當提交表單時隱藏域中的值也會傳送到伺服器。
<input type='hidden' name='name' value='lmy'></input>
我們先考慮一下它相對於網址重寫的優點:
  • 資訊不用直接卸寫在URL後面了,也就說可以穿衣服了(雖然有點少);
  • 資訊的字元數量不受限制;
沒有解決的問題:
  • 數量太多:每個頁面都得寫一個隱藏域,所以在跨越多個頁面傳遞資訊時也是不方便的;
新引入的問題:
  • 既然是使用隱藏域,那麼必須有表單,這就給它的使用帶來了很大限制;

三、 Cookie

鑑於以上兩種技術沒有很好的解決保持Http連線的問題,cookie出現了,而且Http協議也對cookie進行支援,它是嵌入到Http請求頭的,所以它的傳輸由http協議處理。 那麼cookie是什麼?它只是在Web伺服器和客戶端之間來回傳遞的一小塊資訊。在客戶端初次訪問Web伺服器時,由web伺服器在Http響應訊息頭set-Cookie中將cookie傳送給客戶端,一旦客戶端(瀏覽器)儲存了某個cookie,那麼以後每次訪問該web伺服器時都會在http請求頭Cookie欄位中將該資訊回傳給伺服器,它的特點:
  • 一個cookie只能標識一種資訊,該資訊用一對鍵值對錶示;
  • 一個web伺服器可以給客戶端(瀏覽器)傳送多個Cookie,一個客戶端(瀏覽器)也可以儲存多個伺服器提供的Cookie;
  • 瀏覽器一般只允許存放300個cookie,每個伺服器最多存放20個cookie,每個cookie的大小限制為4kB;
  • 該功能是可以被客戶端(瀏覽器)禁用的;
  • 不可跨域名性:即哪個伺服器返回的cookie,那麼這個cookie就只能用於哪個伺服器,即www.google.com返回的cookie不能被www.baidu.com讀取;
建立一個cookie如下:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	Cookie cookie = new Cookie("name", "lmy86263");
	cookie.setComment("auth purpose");
	cookie.setMaxAge(3600);
	resp.addCookie(cookie);
	resp.getWriter().write("cookie return");
}
注意預設情況下,該cookie是一個session級別的cookie,它儲存在客戶端程式(瀏覽器)的記憶體中,當客戶端程式關閉時則被刪除,如果要儲存在磁碟上則要設定maxAge屬性,這樣cookie就以文字檔案的形式儲存在本地了;還要注意實際情況中的cookie的name和value都是加密的,讓你猜不出是什麼意思。

獲取請求回傳的cookie:

Cookie[] cookies = req.getCookies();
if(cookies != null){
    for(Cookie cookie: cookies){
        //...
    }
}
真是蛋疼,只能返回一個cookie陣列,不能根據鍵返回一個值。 刪除cookie:
Cookie cookie = new Cookie("name", "");
cookie.setMaxAge(0);
resp.addCookie(cookie);
為了在服務端刪除一個cookie,也是挺費事的,得重新生成一個並且把它的生存時間設定為0才能在服務端刪除cookie。 cookie的作用範圍: cookie的作用範圍是什麼意思?簡單來說就是cookie在伺服器上能夠被識別的頁面有哪些,因為伺服器返回的cookie只能作用當前的目錄和它的子目錄,不能作用當前目錄的父目錄。想想就覺得有問題,如果你在某寶的開始頁面沒有登入,而是先瀏覽著,後來發現中意的東西要登入付款,結果登陸後不能發現訪問之前的頁面還需要再登入一次,有沒有很氣憤。如果想讓cookie作用於整個web服務提供的頁面,就要使用setPath()這個API:
cookie.setPath(request.getContextPath());
這樣就使得該cookie對web應用的所有的目錄都是有效的。
下面看一下cookie的傳遞過程: 第一次訪問某個web應用: 第二次再訪問該伺服器時:

四、 HttpSession

1、 初識HttpSesion

在追蹤Session的所有的技術中,HttpSession最為強大,不向Cookie一樣將與伺服器保持聯絡的資訊儲存在本地磁碟上,Session的資訊是儲存在伺服器的記憶體上的。用它來保持與客戶端的聯絡是通過如下的方式完成的:當客戶端傳送過來一個要求建立session的請求時,伺服器首先檢查該傳送過來的請求是否有session識別符號,也就是sessionId,如果攜帶有該識別符號,則伺服器程式根據該id查詢到對應的session物件;如果沒有攜帶sessionId,則伺服器認為該客戶端之前沒有傳送過請求(也可能是客戶端長時間不活動導致session過期,沒當做垃圾回收了)併為它生成一個新的session 物件,同時將該物件的識別符號sessionId作為相應的一部分發送給客戶端,這部分是通過cookie技術實現的,也即伺服器將sessionId作為一個cookie返回給客戶端,加在Http的請求頭的Set-Cookie部分。
在伺服器上設定session,由於是第一次訪問之前沒有設定過session:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	HttpSession session = req.getSession();
	resp.getWriter().write("session return ");
}
只是使用如下程式碼即可:
HttpSession session = req.getSession();
request.getSession()會檢查是否存在sessionId,如果沒有則會重新建立一個,並隨響應一起返回,如果有的話則返回該Id對應的session物件,還有一個類似的API:
HttpSession session = req.getSession(true);
getSession(true)和getSession()作用一樣,但是如果是getSession(false)則會在響應中沒有sessionId時返回null。 詳細的過程看如下圖:

上圖是傳送一個沒有sessionId的請求,結果在響應中發現在Set-cookie中返回一個名為JSESSIONID的cookie。
上圖是在向同一個網站傳送第二次請求時的請求頭與響應頭,可以看出伺服器已經檢測出存在sessionId,所以沒有新生成,並且可以看到在請求頭中的Cookie中的JSESSIONID的值和第一次傳送回來JSESSIONID的值一樣。在以後的每一個傳送的請求中都會攜帶有這樣的JSESSIONID,除非session過期被當成垃圾回收了,這時候就要重新建立一個。注意在session中使用的cookie是存在於記憶體中的,稱為session cookie,如果客戶端程式關閉則該cookie消失,其實我們可以將該session cookie持久化到記憶體,因為它是通過cookie來儲存的,所以我們通過cookie來進行持久化,如下:
Cookie cookie = new Cookie("JSESSIONID", session.getId());
cookie.setMaxAge(60);
resp.addCookie(cookie);
這樣就可以將JSESSIONID持久化到硬碟上,不過這樣是有安全問題的。

2、 Session的生命週期

前面我們已經說了,session在服務上是儲存在記憶體中的,如果一個網站的訪問人數很多,那就需要建立很多的session,這對伺服器造成的負擔很大,但是偏偏有些人訪問一次後之後就不再訪問了,但是我們還儲存有他們留下來的session,這就浪費了伺服器的資源了,我我們通過設定session的生命週期來管理每個session。 session的生命週期無非包含兩個問題:
  • 這個session什麼時候產生?
之前說過這個問題,在客戶端(瀏覽器)第一次訪問web應用程式的資源的時候,如果要求使用session,會建立session物件,並返回sessionId。
  • 這個session什麼時候死亡?
  1. 當呼叫session的invalidate()方法時;
  2. 當在伺服器上的web應用程式停止執行時;
  3. 當不活動時間超出session的過期時間;

3、 Session的過期時間

session的過期時間可以通過三種方式來設定:
  • 通過API來設定,單位s:
session.setMaxInactiveInterval(3600);
  • 通過web.xml進行配置,單位min:
<session-config>
	<session-timeout>60</session-timeout>
</session-config>
  • 通過配置所有的web.xml檔案的父檔案來改變,如果預設在自己的工程中沒有設定session的過期時間,則會直接使用servlet容器提供的預設的過期時間,該檔案在tomcat的conf;
  <!-- ==================== Default Session Configuration ================= -->
  <!-- You can set the default session timeout (in minutes) for all newly   -->
  <!-- created sessions by modifying the value below.                       -->

    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>

4、 重寫URL

通過cookie來傳回sessionId時可能會遇到一個問題,就是客戶端(瀏覽器)禁止使用cookie,雖然普通使用者都不會這樣做,但不排除一些專業人士,如果這樣我們就沒有辦法傳回sessionId,這時候可以通過重寫URL的方式來實現,URL地址重寫的原理是將該使用者Session的id資訊重寫到URL地址中。HttpServletResponse中的encodeURL()來編碼要返回的URL,它會自動判斷客戶端(瀏覽器)是否支援cookie然後決定返回的URL是什麼型別的。
  • 如果瀏覽器支援cookie,則返回正常的URL;
  • 如果瀏覽器不支援cookie,則會返回URL+";jsessionid=******"型別的URL
返回的URL類似如下所示:
<a href="index.jsp;jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E">點我</a>
這種方式和之前的網址重寫一樣,每次傳送請求時都要攜帶該jsessionid值

5、 session API

HttpSession提供了很多的API,在這其中比較重要的有兩個getAttribute()和setAttribute(),使用這兩個方法可以儲存一些簡短的資訊,比如訪問計數:
HttpSession session = request.getSession();
//輸出sessionID
System.out.println(session.getId());
//獲取繫結的計數器
Integer count=(Integer) session.getAttribute("count");
if(count==null){
    count=1;
}else{
    count++;
}
//在session中繫結計數器
session.setAttribute("count", count);
另外session還有好多的具體的應用我們下一篇再進一步說明。
相關文章: