跟踪客户状态
Web服务器跟踪客户状态通常有四种办法
- 建立含有跟踪数据的隐藏字段(<input type="hidden" name="name1" value="value1">)
- 重写包含额外参数的URL
- 使用持续的Cookie
- 使用Servlet API中的Session(会话)机制
其中第四种是我们研究的重点
Session的概念
Session用于跟踪客户的状态。Session指的是在一段时间内,单个客户与Web服务器的一连串交互过程。在一个Session中,客户可能会多次请求访问同一个网页,也有可能请求访问各种不同的服务器资源。
Session的例子
- 在电子邮件应用中,从一个客户登录到电子邮件系统开始,经过收信、写信和发信等一系列操作,直至最后退出邮件系统,整个过程为一个Session。
- 在购物网站应用中,从一个客户开始购物,到最后结账,整个过程为一个Session。
Session的运行机制
- 当一个Session开始时,Servlet容器将创建一个HttpSession对象,在HttpSession对象中可以存放客户状态的信息(例如购物车)。
- Servlet容器为HttpSession分配一个唯一标识符,称为Sessio ID。Servlet容器把Session ID作为Cookie保存在客户的浏览器中。
- 每次客户发出HTTP请求时,Servlet容器可以从HttpServletRequest对象中读取Session ID,然后根据Session ID找到相应的HttpSession对象,从而获取客户的状态信息。
HttpSession接口
- getId()返回Session的ID
- invalidate()使当前的Session失效,Servlet容器会释放HttpSession对象占用的资源
- setAttribuate(String name, Object value)将一对name/value属性保存在HttpSession对象中
- getAttribute(String name)根据name参数返回保存在HttpSession对象中的属性值
- isNew()判断是否是新创建的Session。如果是新创建的Session,返回true,否则返回false
- setMaxInactiveInterval()设定一个Session可以处于不活动状态的最大时间间隔,以秒为单位,如果超过这个时间,Session会自动失效,如果设置为负数,表示不限制Session处于不活动状态的时间
Session的生命周期
- 当客户第一次访问Web应用中支持Session的某个网页时,就会开始一个新的Session。接下来当客户浏览这个Web应用得不同网页时,始终处于同一个Session中
- 默认情况下,JSP网页都是支持Session的,也可以通过显示声明支持Session:<%@ page session="true">
在以下情况中,Session将结束生命周期,Servlet容器会将Session所占用的资源释放掉
- 客户端关闭浏览器(当用户第一次访问页面的时候,服务器检查到第一次访问并为其创建一个Session,生成一个Session ID,接着服务器端向客户端发出一个响应,把Session ID作为响应的一部分以Cookie的方式发送给客户端,下一次客户端再次访问这个Web应用时,会把返回来的Session ID这个Cookie信息有发送给服务器端,服务器检查这个Session ID与服务器内存中保存的ID一一匹配,用户处于匹配的那个Session下面。会话Session作为一个Cookie存放在浏览器的进程中,并不存放在硬盘上,浏览器关闭,Session ID就没有了。此外,服务器端并不知道客户端的浏览器关掉了,因此旧的Session依然存在,占据内存,因此默认的Session的有效期限是30分钟)
- Session过期
- 服务器端调用了HttpSession的invalidate()方法
Session过期
Session过期是指当Session开始后,在一段时间内客户没有和Web服务器交互,这个Session会失效,HttpSession的setMaxInactiveInterval()方法可以设置允许Session保持不活动时间的状态(以秒为单位),如果超过这一时间,Session就会失效。
Session范例程序
创建一个简单的邮件系统,由3个JSP文件组成:
<%--maillogin.jsp --%> <%@ page contentType="text/html; charset=GB2312" pageEncoding="gb2312"%> <%@ page session="true" %> <html> <head> <title>session练习</title> </head> <body bgcolor="#FFFFFF" onLoad="document.loginForm.username.focus()"> <% String name=""; if(!session.isNew()){ name=(String)session.getAttribute("username"); if(name==null)name=""; } %> <p>欢迎光临邮件系统</p> <p>Session ID:<%=session.getId()%></p> <form name="loginForm" method="post" action="mailcheck.jsp"> <table width="500" border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table width="500" border="0" cellspacing="0" cellpadding="0"> <tr> <td width="401"><div align="right">User Name: </div></td> <td width="399"><input type="text" name="username" value=<%=name%>></td> </tr> <tr> <td width="401"><div align="right">Password: </div></td> <td width="399"><input type="password" name="password"></td> </tr> <tr> <td width="401"> </td> <td width="399"><br><input type="Submit" name="Submit" value="提交"></td> </tr> </table> </td> </tr> </table> </form> </body> </html> <%--mailcheck.jsp --%> <%@ page contentType="text/html; charset=GB2312" pageEncoding="gb2312"%> <%@ page session="true" %> <html> <head> <title> checkmail </title> </head> <body> <% String name=null; name=request.getParameter("username"); if(name!=null)session.setAttribute("username",name); %> <a href="maillogin.jsp">登录</a> <a href="maillogout.jsp">注销</a> <p>当前用户为:<%=name%> </P> <P>你的信箱中有52封邮件</P> </body> </html> <%--maillogout.jsp --%> <%@ page contentType="text/html; charset=GB2312" pageEncoding="gb2312"%> <%@ page session="true" %> <html> <head> <title> maillogout </title> </head> <body> <% maString name=(String)session.getAttribute("username"); session.invalidate(); %> <%=name%>,再见! <a href="maillogin.jsp">重新登录邮件系统</a> </body> </html>
三个页面显示情况如下(注意注销后,重新登录Session ID变化了,这是因为在maillogout中我们将其Session销毁了):
练习题
分配HttpSession ID是服务器的行为,而不是JavaWeb应用程序,因此C错;至于A,因为默认每一个页面开启Session,我们可以将Session属性设置为false,网页便不再支持Session。
观察者模式(Observer)
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,让他们能够自动更新自己。
观察者模式组成
- 抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类或接口来实现。
- 抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
- 具体主题角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
- 具体观察者角色:该角色实现抽象观察者角色所要求更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。通常用一个子类实现。
一个实例:
package com.test.observer; public interface Subject { public void attach(Observer observer); public void detach(Observer observer); public void notifyObservers(); } package com.test.observer; public abstract class Observer { public abstract void update(Object object); } package com.test.observer; public class ConcreteObserver extends Observer { @Override public void update(Object object) { System.out.println("观察者更新自己"); System.out.println(object); } } package com.test.observer; import java.util.ArrayList; import java.util.List; public class ConcreteSubject implements Subject { List<Observer> list = new ArrayList<Observer>(); public void attach(Observer observer) { list.add(observer); } public void detach(Observer observer) { list.remove(observer); } public void notifyObservers() { for(Observer observer : list) { observer.update("通知观察者"); } } public void a() { this.notifyObservers(); } } package com.test.observer; public class Client { public static void main(String[] args) { ConcreteSubject subject = new ConcreteSubject(); ConcreteObserver o1 = new ConcreteObserver(); ConcreteObserver o2 = new ConcreteObserver(); ConcreteObserver o3 = new ConcreteObserver(); subject.attach(o1); subject.attach(o2); subject.attach(o3); subject.a(); subject.detach(o1); System.out.println("------------------"); subject.a(); } } /* output: 观察者更新自己 通知观察者 观察者更新自己 通知观察者 观察者更新自己 通知观察者 ------------------ 观察者更新自己 通知观察者 观察者更新自己 通知观察者 */
Listener
Listener是Servlet的监听器,它可以监听客户端的请求、服务端的操作等。通过监听器,可以自动激发一些操作,比如监听在线的用户的数量。当增加一个HttpSession时,就激发sessionCreted(HttpSessionEvent se)方法,这样就可以给在线人数加1。
常用的监听接口有以下几个:
- ServletContextAttributeListener监听对ServletContext属性的操作,比如增加、删除、修改属性
- ServletContextListener监听ServletContext,当创建ServletContext时,激发contextInitialized(ServletContextEvent sce)方法;当销毁ServletCOntext时,激发contextDestroyed(ServletContextEvent sce)方法
HttpSessionListener监听HttpSession的操作。当创建一个Session时,激发sessionCreated(HttpSessionEvent se)方法;当销毁一个Session时,激发sessionDestroyed(HttpSessionEvent se)方法
- HttpSessionAttributeListener监听HttpSession中属性的操作。当在Session增加一个属性时,激发attributeAdded(HttpSessionBindingEvent se)方法;当在Session删除一个属性时,激发attributeRemoved(HttpSessionBindingEvent se)方法;当Session属性被重新设置时,激发attributeReplaced(HttpSessionBIndingEvent se)方法
一个实例:
package com.test.listener; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class OnLineCountListener implements HttpSessionListener, ServletContextListener, ServletContextAttributeListener { private int count; // 用户数量 public void sessionCreated(HttpSessionEvent arg0) // 用户访问这个web应用下的任何一个页面或者JSP时,该方法自动被调用(观察者角色) { count++; setContext(arg0); System.out.println("count"); } public void sessionDestroyed(HttpSessionEvent arg0) { count--; setContext(arg0); } public void setContext(HttpSessionEvent se) { se.getSession().getServletContext().setAttribute("onLine", new Integer(count)); // 触发增加或者替换操作 } public void contextInitialized(ServletContextEvent arg0) { log("contextInitialized()"); } public void contextDestroyed(ServletContextEvent arg0) { log("contextDestroyed()"); } public void attributeAdded(ServletContextAttributeEvent arg0) { log("attributeAdded(‘" + arg0.getName() + "‘, ‘" + arg0.getValue() + "‘)"); } public void attributeRemoved(ServletContextAttributeEvent arg0) { log("attributeRemoved(‘" + arg0.getName() + "‘, ‘" + arg0.getValue() + "‘)"); } public void attributeReplaced(ServletContextAttributeEvent arg0) { log("attributeReplaced(‘" + arg0.getName() + "‘, ‘" + arg0.getValue() + "‘)"); } private void log(String message) { System.out.println("ContextListener: " + message); } }
在web.xml中配置以下信息:
<listener>
<listener-class>com.test.listener.OnLineCountListener</listener-class>
</listener>
服务器一启动,输出ContextListener: contextInitialized(),这是我们写的contextInitialized()方法被调用了。接着访问Web应用下的任何一个JSP页面,命令行输出ContextListener: attributeAdded(‘onLine‘, ‘1‘)
count,关闭浏览器在打开,访问JSP页面,输出:
ContextListener: attributeReplaced(‘onLine‘, ‘1‘)
count,可能我们会有一个疑惑,这里在线人数应该是2,这个问题和getValue方法实现是相关的,下面的文字摘自J2EE API:
getValue
public java.lang.Object getValue()
- Gets the value of the ServletContext attribute that changed.
If the attribute was added, this is the value of the attribute. If the attribute was removed, this is the value of the removed attribute. If the attribute was replaced, this is the old value of the attribute.
-
- Returns:
- the value of the ServletContext attribute that changed
可以看到被替换后的返回值为旧值。