第九章 会话跟踪
核心方法:
1.只能在发送任何文档内容到客户程序之前调用request.getSession,在之后调用,则会影响到后面的响应。
警告:
1.如果我们使用URL重写进行会话跟踪,我们的页面最好不要使用静态页面,或者静态页面至少不包含指向自身站点的动态链接。
学习笔记
一、会话跟踪的需求
1.首先什么是会话:
会话首先是一个过程,引用百度百科的说法:
引用
web会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话。当然用户开不同的浏览器,其会话也不同
。这也就可以解释什么是会话级别的cookie了。
而我的理解是
会话还有一层概念:就是会话就是一个类似Map的对象(session)。
那么什么是会话跟踪,我的理解就是利用会话去跟踪用户访问这个网站的一系列操作。联想购物车来理解这个会话跟踪的概念。
2.HTTP是“无状态”的协议,也就是没有像FTP的那种连接,断开的状态之分,客户每次读取web页面,都是打开web服务器的单独的连接,而且,服务器也不自动维护客户的上下文信息(可以理解为:服务器也没有这么多的精力去维护)。
3.这里要理解一下上下文,上下文我们可以把它理解为一个共享对象。上下文中的名字都由一个相关联的对象组成,同一个应用程序中任何程序都可以从一个上下文中通过名字获得相关联的对象,一个应用程序中的SERVLET还经常需要在一个HTTP请求外来共享某些信息,因此为了管理这些对象的共享周期,容器提供了3个标准的上下文:应用程序上下文,HTTP请求上下文,会话上下文。一个页面范围内的上下文仅仅对一个页面有效。在页面范围内的对象不可能和其他JSP和SERVLET共享。具体可以参见
http://fanlifeng.iteye.com/blog/690827
4.如何把一个用户之前创建购物车和用户进行关联,类似的,如何知道购物车已经放了哪些东西。可以使用四种解决方式,
a.cookie,每一个用户有一个特殊的ID,通过这个ID我们可以找到存放在服务器上的用户信息。但是,它所需的工作量比较大,需要我们提取cookie,并且确定空闲会话什么时候过期,还要把散列表和请求关联起来(就是根据cookie中的会话ID与sessionInfo散列表中的用户具体数据关联起来,这里实际上使用的是两个表,一个是会话ID和用户关联起来的表,一个是存放具体用户数据的表,也就是说我们根据关键码获得这个用户数据的存放位置,然后我们去具体的这个位置查找相应的表得出用户数据),并生成唯一的会话标识符。
b.使用URL链接,这种方式就是在URL后加上一个用户的ID,通过该ID,我们可以得到服务器上的用户信息。比如http://host/path/file.html;jsessionid=a1234 这样可以解决浏览器不支持cookie或在用户禁用cookie的情况下。但是,我们同样遇到了工作量大的问题。其次,我们就必须在所以涉及用户信息的URL中加上用户的标识符,这样我们就不能使用静态页面(至少不能有任何链接到站点动态页面的链接,因为我们无法通过静态页面把用户信息加到动态页面中去),因为静态页面不需要通过服务器加载,所以我们就不能通过服务器把用户名加到URL上去。静态页面是后缀为html或htm的页面。c.隐藏的表单域,使用
<input type = "HIDDEN" name = "session" value = "a1234">
但是我们只有在提交表单时才使用这种方法,而且<a href>不能产生表单提交的效果。d.我们可以使用会话跟踪解决方案,HttpSession API
,这个高层建筑在cookie和重写URL之上,这个方法十分的方便。
二、会话跟踪基础
如何使用会话?见如下四步
(一)访问与当前请求相关联的会话对象。request.getSession获取HttpSession 对象,该对象是一个简单的散列表。用来存储用户的相关数据。在后台,系统从cookie或附加的URL数据中提取出用户ID,之后以该ID为键,访问之前创建的HttpSession对象组成的表,但是,这些动作对程序员都是透明的,我们只需调用getSession,如果我们在输入cookie或附件URL信息中找不到会话ID,系统则会创建一个新的空会话,同时如果,我们使用cookie(默认),系统还会创建一个名为JSESSIONID的输出cookie,在其中存入唯一的值表示会话ID。因此只能在发送任何文档内容到客户程序之前调用request.getSession,在之后调用,则会影响到后面的响应。这里要注意request.getSession()等同于request.getSession(true);意思就是如果不存在会话,则创建一个新的会话,如果为request.getSession(false)代表我们只想打印出那些在会话中已经存在的信息。
(二)访问与会话相关联的信息
会话对象拥有内建的数据结构(散列表),其中存储任意数量的键和与键相关联的值。查找前些时候存储的值使用
session.getAttribute("key")。
HttpSession session = request.getSession();
SomeClass value = (SomeClass)session.getAttribute("someIdentifier");
if(value ==null){
value = new SomeClass(...);
session.setAttribute("someIdentifier",value);
}
doSomethingWith(value);
(三)将信息与会话关联起来。
1.如果要指定信息,则需使用setAttribute。如果希望在这些值存储在会话之前执行边界效应,只需让希望与会话关联的对象实现HttpSessionBindingListener接口(也就是说如果提供给setAttribute的对象实现了HttpSessionBindingListener接口)。这样每次调用setAttribute方法设置它们中间的任何一个对象时,它的valueBound方法都会紧随其后调用。
2.通常,会话属性的类型只要是Object的就可以,但是如果我们使用的应用程序为物理服务器的集群所共享,系统需要能够将会话对象在机器间转移,如果我们的程序运行在这种环境中,并将web应用标记为可分布式执行,则必须满足额外的条件,会话的属性要实现Serializable接口,而这个序列化又是什么意思呢?序列化就是把一个对象的形态改变一下,使他能够存放到文件中,或者在网络上传输。比如读写文件需要序列化,RMI也需要。
(四)废弃会话数据
1.只移出自己编写的servlet创建的数据,removeAttribute("key")废弃与指定键关联的值。
2.删除整个会话:我们可以调用invalidate,将整个会话删除,但是这样会丢失用户的所有会话数据。而且要注意如果作废一个会话,有可能会破坏其他servlet或jsp页面正在使用的数据。
3.将用户从系统中注销,并删除他的所有会话:我们可以调用logout方法,将客户从web服务器中注销。同时废弃所有相关联的会话
四、浏览器会话与服务器会话
也就是存放在浏览器中的会话和存放在服务器中的会话
如果我们使用的会话cookie,当浏览器退出时就会造成会话中断,客户程序就不能再访问会话,但是服务器并不知道浏览器已经关闭,所以它还会把维护会话一定时间,就好像是超市的员工看到一个购物车旁边没有人就一定代表员工可以把购物车中的东西放在货架上一样。当客户访问的时间间隔超过getMaxInactiveInterval指定的时间之后,会话自动变为不活动状态,我们就可以删除该会话。同时如果我们明确告诉服务器删除会话或是将用户从系统中注销时,服务器就可以删除该会话。
五、对发往客户的URL进行编码
因为servlet容器(引擎)使用cookie作为会话跟踪的底层机制。但假定我们改使用URL重写,代码该如何更改。
我们有两种情况会使用自身站点的URL,这时我们就要检查是否使用了URL重写技术。从而把会话标识放在URL中
1.在servlet生成的web页面中含有嵌入的URL,我们应该将URL传递给HttpServletResponse的encodeURL方法,这个方法确定当前是否使用URL重写,仅在必要时附加会话信息。
String originalURL = someRelativeOrAbsoluteURL;
String encodedURL = response.encodeURL(originalURL);
out.println("<a href =\""+encodedURL+"\">"...</a>");
2.第二种情况是在sendRedirect调用中。这时使用encodeRedirectURL
String originalURL = someURL;
String encodedURL = response.encodeRedirectURL(originalURL);
response.sendRedirect(encodedURL);
encodeURL的用法:
引用
Encodes the specified URL by including the session ID in it, or, if encoding is not needed, returns the URL unchanged. The implementation of this method includes the logic to determine whether the session ID needs to be encoded in the URL. For example, if the browser supports cookies, or session tracking is turned off, URL encoding is unnecessary.
六显示客户访问计数的servlet
HttpSession session = request.getSession();
Integer accessCount = (Integer)session.getAttribute("accessCount");
String heading = "";
if(accessCount==null){
heading = "Welcome new User";
accessCount = new Integer(0);
}else{
heading = "Welcome Back";
accessCount = new Integer(accessCount.intValue()+1);
}
session.setAttribute("accessCount", accessCount);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out
.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01
Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.println("<H1>"+heading+"</H1>");
out.println("<H1>"+cookieID+"</H1>");
out.println("<table>");
out.println("<tr><td>SessionID:"+session.getId()+"</td><tr>");
out.println("<tr><td>CreationTime:"+new Date(session.getCreationTime())+"</td></tr>");
out.println("<tr><td>Time Of Last Access:"+new Date(session.getLastAccessedTime
())+"</td></tr>");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
七简单购物车的实现
关键代码
<H4>Order Form</H4>
<form action = "coreservlets.showItems" >
<table>
<tr>
<td>New Item to Order:</td>
<td><input type = "textfield" name = "itemName" ></td>
</tr>
<tr>
<td><input type = "submit" value = "submit"></td>
<td><input type = "reset" value = "reset"></td>
</tr>
</table>
</form>
String itemName = request.getParameter("itemName");
String title =null;
ArrayList<String>itemNames=null;
HttpSession session = request.getSession();
itemNames = (ArrayList<String>)session.getAttribute("itemNames");
if(itemNames==null||itemNames.size()==0){
title = "Please select your goods";
itemNames = new ArrayList<String>();
}else{
if(itemName==null||itemName.equals("")){
title = "Please select your goods,and this is your selected goods";
}else{
title = "This is your selected goods";
}
}
itemNames.add(itemName);
session.setAttribute("itemNames", itemNames);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out
.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01
Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.println("<h1>"+title+"</h1>");
out.println("<table>");
for(int i = 0 ; i < itemNames.size() ;i++){
out.println("<tr><td>"+itemNames.get(i)+"</td></tr>");
}
out.println("</table>");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();