您的位置:寻梦网首页编程乐园JAVA 天地Servlet 专辑Servlet与JSP教程
Java 天地

Servlet与JSP教程

第一章 Servlet和JSP概述
第二章 设置开发、运行环境
第三章 第一个Servlet
第四章 处理表单数据
第五章 读取HTTP请求头
第六章 访问CGI变量
第七章 HTTP应答状态
第八章 设置HTTP应答头
第九章 处理Cookie
第十章 会话状态
第十一章 JSP及语法概要
第十二章 脚本元素、指令和预定义变量
第十三章 JSP动作

Servlet与JSP教程(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表单中可以包含下面这样的输入域:<INPUT TYPE="HIDDEN" NAME="session" VALUE="...">。这意味着,当表单被提交时,隐藏域的名字和数据也被包含到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) +
"<BODY BGCOLOR=\"#FDF5E6\">\n" +
"<H1 ALIGN=\"CENTER\">" + heading + "</H1>\n" +
"<H2>Information on Your Session:</H2>\n" +
"<TABLE BORDER=1 ALIGN=CENTER>\n" +
"<TR BGCOLOR=\"#FFAD00\">\n" +
" <TH>Info Type<TH>Value\n" +
"<TR>\n" +
" <TD>ID\n" +
" <TD>" + session.getId() + "\n" +
"<TR>\n" +
" <TD>Creation Time\n" +
" <TD>" + new Date(session.getCreationTime()) + "\n" +
"<TR>\n" +
" <TD>Time of Last Access\n" +
" <TD>" + new Date(session.getLastAccessedTime()) + "\n" +
"<TR>\n" +
" <TD>Number of Previous Accesses\n" +
" <TD>" + accessCount + "\n" +
"</TABLE>\n" +
"</BODY></HTML>");
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

(作者:仙人掌工作室  来源:仙人掌工作室)