Java Servlet和JSP教程(10)(轉)

發表於2007-08-11
Java Servlet和JSP教程(10)(轉)[@more@]

此篇是本站CnJSP小組核心成員鈍刀原創作品

10 會話

10.1 會話狀態概述

HTTP 協議的“無狀態”(Stateless)特點帶來了一系列的問題。特別是透過線上商店購物時,伺服器不能順利地記住以前的事務就成了嚴重的問題。它使得 “購物籃”之類的應用很難實現:當我們把商品加入購物籃時,伺服器如何才能知道籃子裡原先有些什麼?即使伺服器儲存了上下文資訊,我們仍舊會在電子商務應用中遇到問題。例如,當使用者從選擇商品的頁面(由普通的伺服器提供)轉到輸入信用卡號和送達地址的頁面(由支援SSL的安全伺服器提供),伺服器如何才能記住使用者買了些什麼?

這個問題一般有三種解決方法:

Cookie。利用HTTP Cookie來儲存有關購物會話的資訊,後繼的各個連線可以檢視當前會話,然後從伺服器的某些地方提取有關該會話的完整資訊。這是一種優秀的,也是應用最廣泛的方法。然而,即使Servlet提供了一個高階的、使用方便的Cookie介面,仍舊有一些繁瑣的細節問題需要處理:

從其他Cookie中分別出儲存會話標識的Cookie。

為Cookie設定合適的作廢時間(例如,中斷時間超過24小時的會話一般應重置)。

把會話標識和伺服器端相應的資訊關聯起來。(實際儲存的資訊可能要遠遠超過儲存到Cookie的資訊,而且象信用卡號等敏感資訊永遠不應該用Cookie來儲存。)

改寫URL。你可以把一些標識會話的資料附加到每個URL的後面,伺服器能夠把該會話標識和它所儲存的會話資料關聯起來。這也是一個很好的方法,而且還有當瀏覽器不支援Cookie或使用者已經禁用Cookie的情況下也有效這一優點。然而,大部分使用Cookie時所面臨的問題同樣存在,即伺服器端的程式要進行許多簡單但單調冗長的處理。另外,還必須十分小心地保證每個URL後面都附加了必要的資訊(包括非直接的,如透過Location給出的重定向 URL)。如果使用者結束會話之後又透過書籤返回,則會話資訊會丟失。

隱藏表單域。HTML表單中可以包含下面這樣的輸入域: 。這意味著,當表單被提交時,隱藏域的名字和資料也被包含到GET或POST資料裡,我們可以利用這一機制來維持會話資訊。然而,這種方法有一個很大的缺點,它要求所有頁面都是動態生成的,因為整個問題的核心就是每個會話都要有一個唯一識別符號。

Servlet為我們提供了一種與眾不同的方案: HttpSession API。HttpSession API是一個基於Cookie或者URL改寫機制的高階會話狀態跟蹤介面:如果瀏覽器支援 Cookie,則使用Cookie;如果瀏覽器不支援Cookie或者Cookie功能被關閉,則自動使用URL改寫方法。Servlet開發者無需關心細節問題,也無需直接處理Cookie或附加到URL後面的資訊,API自動為Servlet開發者提供一個可以方便地儲存會話資訊的地方。

10.2 會話狀態跟蹤API

在Servlet中使用會話資訊是相當簡單的,主要的操作包括:檢視和當前請求關聯的會話物件,必要的時候建立新的會話物件,檢視與某個會話相關的資訊,在會話物件中儲存資訊,以及會話完成或中止時釋放會話物件。

10.2.1 檢視當前請求的會話物件

檢視當前請求的會話物件透過呼叫HttpServletRequest的getSession方法實現。如果getSession方法返回null,你可以建立一個新的會話物件。但更經常地,我們透過指定引數使得不存在現成的會話時自動建立一個會話物件,即指定getSession的引數為true。因此,訪問當前請求會話物件的第一個步驟通常如下所示:

HttpSession session = request.getSession(true);

10.2.2 檢視和會話有關的資訊

HttpSession 物件生存在伺服器上,透過Cookie或者URL這類後臺機制自動關聯到請求的傳送者。會話物件提供一個內建的資料結構,在這個結構中可以儲存任意數量的鍵-值對。在2.1或者更早版本的Servlet API中,檢視以前儲存的資料使用的是getValue("key")方法。getValue返回 Object,因此你必須把它轉換成更加具體的資料型別。如果引數中指定的鍵不存在,getValue返回null。

API 2.2 版推薦用getAttribute來代替getValue,這不僅是因為getAttribute和setAttribute的名字更加匹配(和 getValue匹配的是putValue,而不是setValue),同時也因為setAttribute允許使用一個附屬的 HttpSessionBindingListener 來監視數值,而putValue則不能。

但是,由於目前還只有很少的商業Servlet引擎支援2.2,下面的例子中我們仍舊使用getValue。這是一個很典型的例子,假定ShoppingCart是一個儲存已購買商品資訊的類:

HttpSession session = request.getSession(true);

ShoppingCart previousItems =(ShoppingCart)session.getValue("previousItems");

if (previousItems != null) {

doSomethingWith(previousItems);

} else {

previousItems = new ShoppingCart(...);

doSomethingElseWith(previousItems);

}

大多數時候我們都是根據特定的名字尋找與它關聯的值,但也可以呼叫getValueNames得到所有屬性的名字。getValuesNames返回的是一個String陣列。API 2.2版推薦使用getAttributeNames,這不僅是因為其名字更好,而且因為它返回的是一個 Enumeration,和其他方法(比如HttpServletRequest的getHeaders和getParameterNames)更加一致。

雖然開發者最為關心的往往是儲存到會話物件的資料,但還有其他一些資訊有時也很有用。

getID:該方法返回會話的唯一標識。有時該標識被作為鍵-值對中的鍵使用,比如會話中只儲存一個值時,或儲存上一次會話資訊時。

isNew:如果客戶(瀏覽器)還沒有繫結到會話則返回true,通常意味著該會話剛剛建立,而不是引用自客戶端的請求。對於早就存在的會話,返回值為false。

getCreationTime:該方法返回建立會話的以毫秒計的時間,從1970.01.01(GMT)算起。要得到用於列印輸出的時間值,可以把該值傳遞給Date建構函式,或者GregorianCalendar的setTimeInMillis方法。

getLastAccessedTime:該方法返回客戶最後一次傳送請求的以毫秒計的時間,從1970.01.01(GMT)算起。

getMaxInactiveInterval:返回以秒計的最大時間間隔,如果客戶請求之間的間隔不超過該值,Servlet引擎將保持會話有效。負數表示會話永遠不會超時。

10.2.3 在會話物件中儲存資料

如上節所述,讀取儲存在會話中的資訊使用的是getValue方法(或,對於2.2版的Servlet規範,使用getAttribute)。儲存資料使用putValue(或setAttribute)方法,並指定鍵和相應的值。注意putValue將替換任何已有的值。有時候這正是我們所需要的(如下例中的referringPage),但有時我們卻需要提取原來的值並擴充它(如下例previousItems)。示例程式碼如下:

HttpSession session = request.getSession(true);

session.putValue("referringPage", request.getHeader("Referer"));

ShoppingCart previousItems = (ShoppingCart)session.getValue("previousItems");

if (previousItems == null) {

previousItems = new ShoppingCart(...);

}

String itemID = request.getParameter("itemID");

previousItems.addEntry(Catalog.getEntry(itemID));

session.putValue("previousItems", previousItems);

10.3 例項:顯示會話資訊

下面這個例子生成一個Web頁面,並在該頁面中顯示有關當前會話的資訊。

package hall;

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import java.net.*;

import java.util.*;

public class ShowSession extends HttpServlet {

public void doGet(HttpServletRequest request,HttpServletResponse response)

throws ServletException, IOException {

HttpSession session = request.getSession(true);

response.setContentType("text/html");

PrintWriter out = response.getWriter();

String title = "Searching the Web";

String heading;

Integer accessCount = new Integer(0);;

if (session.isNew()) {

heading = "Welcome, Newcomer";

} else {

heading = "Welcome Back";

Integer oldAccessCount =

// 在Servlet API 2.2中使用getAttribute而不是getValue

(Integer)session.getValue("accessCount");

if (oldAccessCount != null) {

accessCount =

new Integer(oldAccessCount.intValue() + 1);

}

}

// 在Servlet API 2.2中使用putAttribute

session.putValue("accessCount", accessCount);

out.println(ServletUtilities.headWithTitle(title) +"

" +

"

" + heading + "

" +

"

Information on Your Session:

" +

"

" +

"

" +

"

" +

"

" +

"

" +

"

" +

"
Info TypeValue

" +

"
ID

" +

"
" + session.getId() + "

" +

"
Creation Time

" +

"
" + new Date(session.getCreationTime()) + "

" +

"
Time of Last Access

" +

"
" + new Date(session.getLastAccessedTime()) + "

" +

"
Number of Previous Accesses

" +

"
" + accessCount + "

" +

"

" +

"

相關文章