/*************************************************************/
/*
wallimn原创文章,欢迎转载,转载请保留本文信息。
博客:http://blog.csdn.net/wallimn 邮件:wallimn@sohu.com
*/
/*************************************************************/
思路很简单,但与网上多数文章有所不同。我使用两个监听器,通过HttpSessionListener监听用户退出事件,通过HttpSessionAttributeListener监听用户登录事件。一般来讲,用户登录,都是向session中写入一个登录用户名。因此,可以通过监听session属性的变化,得到用户登录的情况。监听器配制方法,就是在web.xml加入如下代码:
<listener>
<listener-class>com.wallimn.onlinestat.SessionListener</listener-class>
</listener>
使用工厂模式来设计,定义一个保存用户登录信息的接口(IStatStore),以便可以通过多种机制保存用户登录信息,如内存(MemStat)或数据库(JdbcStat)。定义一个类厂(StoreFactory),来实现保存机制的切换。
注:我在代码中,没有完成JdbcStat这个类,若哪位用我的方法,写了这个类,最好能通过邮件发给我。谢谢。
以上是一些设计思想的简单介绍,下面把几个程序贴一下。
SessionListener.java

/** */ /**
* 文件:SessionListener.java<br/>
* 功能:<br/>
* 编码:wallimn(Email: wallimn@sohu.com QQ: 54871876)<br/>
* 版本:V1.0<br/>
* 时间:2008-3-25<br/>
* 备注:此代码欢迎讨论,转载请保留以上信息。
*/
package com.wallimn.onlinestat;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/** */ /**
* session监听器,通过监听器来实现在线用户统计功能的类<br/>
*
* @version : V1.0<br/>
* @author : wallimn(Email: wallimn@sohu.com QQ: 54871876)<br/>
* @date : 2008-3-25 上午09:41:11<br/>
*/
public class SessionListener implements HttpSessionListener,
HttpSessionAttributeListener ... {
/** *//**
* 这个值应该根据不同的应用,改成不同的值
*/
private static final String LOGIN = "loginusername";

/** *//**
* 功能描述: 编码时间:2008-3-25 上午09:41:11 参数及返回值:
*
* @param arg0
* @see javax.servlet.http.HttpSessionListener#sessionCreated(javax.servlet.http.HttpSessionEvent)
*/
public void sessionCreated(HttpSessionEvent se) ...{
// 时间: 2008-3-25 上午09:41:11
System.out.println("Session创建:"+se.getSession().getId());
}

/** *//**
* 功能描述:session销毁监控,销毁时注销用户 编码时间:2008-3-25 上午09:41:11 参数及返回值:
*
* @param arg0
* @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent)
*/
public void sessionDestroyed(HttpSessionEvent se) ...{
// 时间: 2008-3-25 上午09:41:11
String name = (String) se.getSession().getAttribute(LOGIN);
System.out.println("有人登出:"+name);
if (name != null) ...{
StoreFactory.getStore().logoff(name);
}
}

/** *//**
*
* 功能描述:监控用户登录。有登录的,记录其状态。
* 编码时间:2008-4-3 上午10:58:36
* 参数及返回值:
* @param event
* @see javax.servlet.http.HttpSessionAttributeListener#attributeAdded(javax.servlet.http.HttpSessionBindingEvent)
*/
public void attributeAdded(HttpSessionBindingEvent event) ...{
// 时间: 2008-3-25 上午09:55:42
String name = event.getName();
if (LOGIN.equals(name)) ...{
StoreFactory.getStore().login((String) event.getValue());
System.out.println("有人登录:"+event.getValue());
}
}

public void attributeRemoved(HttpSessionBindingEvent event) ...{
// 时间: 2008-3-25 上午09:55:42
}

public void attributeReplaced(HttpSessionBindingEvent event) ...{
// 时间: 2008-3-25 上午09:55:42
}
} 
接口:IStatStore.java
package com.wallimn.onlinestat;
import java.util.List;
/** */ /**
* 在线人数统计的接口,可以使用多种方式实现,如:内存、数据库等,只要实现这个接口就可以了。
*<br/>
* @version : V1.0<br/>
* @author : wallimn(Email: wallimn@sohu.com QQ: 54871876)<br/>
* @date : 2008-4-4 下午02:01:07<br/>
*/ 
public interface IStatStore ... {
/** *//**
* 记录登录状态
*<br/>
*编码:wallimn 时间:2008-4-3 上午10:08:05<br/>
*参数及返回值:<br/>
* @param username
*/
public void login(String username);
/** *//**
* 取消登录状态
*<br/>
*编码:wallimn 时间:2008-4-3 上午10:08:17<br/>
*参数及返回值:<br/>
* @param username
*/
public void logoff(String username);

/** *//**
* 返回登录用户及信息。
*<br/>
*编码:wallimn 时间:2008-4-3 上午10:20:08<br/>
*参数及返回值:<br/>
* @return链表里的对象是个数组,数组包含两个元素,0:用户名,1:登录时间。这样是为了便于为jstl标签处理
*/
public List getUsers();

/** *//**
* 在线用户数量
*<br/>
*编码:wallimn 时间:2008-4-4 下午01:52:55<br/>
*参数及返回值:<br/>
* @return
*/
public int getCount();
} 
MemStat.java
package com.wallimn.onlinestat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/** */ /**
* 依托内存实现在线用户统计功能。适合于在线用户数量较少的情况。
*<br/>
* @version : V1.0<br/>
* @author : wallimn(Email: wallimn@sohu.com QQ: 54871876)<br/>
* @date : 2008-4-4 下午01:53:36<br/>
*/ 
public class MemStat implements IStatStore ... {
private static Map userMap=null;
static ...{
userMap = new HashMap();
}

public void login(String username) ...{
//时间: 2008-4-3 上午10:09:13
userMap.put(username, new Date());//如果存在,会覆盖已有的值
}

public void logoff(String username) ...{
//时间: 2008-4-3 上午10:09:13
userMap.remove(username);
}

public List getUsers() ...{
//时间: 2008-4-3 上午10:23:34
List list = new LinkedList();
String user = null;
for(Iterator it = userMap.keySet().iterator();it.hasNext();)...{
user = (String) it.next();
list.add(new Object[]...{user,userMap.get(user)});
}
return list;
}

public int getCount() ...{
//时间: 2008-4-4 下午01:53:25
return userMap.size();
}
} 
类厂:StoreFactory.java
package com.wallimn.onlinestat;

public class StoreFactory ... {
/** *//**
* 返回实现IStatStore接口的某种对象。
*<br/>
*编码:wallimn 时间:2008-4-3 上午10:54:05<br/>
*参数及返回值:<br/>
* @return 实现IStatStore接口的某种对象
*/
public static IStatStore getStore()...{
//这里应该设计成从配制文件中读取,暂时先这样写。
//如果想在系统中使用jdbc的方式来实现统计功能,则返回JdbcStat对象即可。
return new MemStat();
}
} 
页面上使用方法示例:

<% ... @ page language="java" pageEncoding="GB18030" %> 
<% ... @ page import="com.wallimn.onlinestat.*" %> 
<% ... @ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 
<% ...
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
request.setAttribute("stat",StoreFactory.getStore().getUsers());
%> 
<% ... --2007-9-20-- %>
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html xmlns ="http://www.w3.org/1999/xhtml" >
< head >
< base href ="<%=basePath%>" />
< title > 在线用户统计 </ title >
< meta http-equiv ="pragma" content ="no-cache" />
< meta http-equiv ="cache-control" content ="no-cache" />
< meta http-equiv ="expires" content ="0" />
< meta http-equiv ="keywords" content ="wallimn" />
< meta http-equiv ="description" content ="在线用户显示,wallimn,2008年4月" /> 
< style > ... 
h1{...}{
text-align:center;
font-size:18px;
margin-top:10px;
margin-bottom:10px;
}
table{...}{
border-collapse:collapse;
font-size:12px;
}
table,td,th{...}{
border:1px solid #EAEAEA;
}
th{...}{
height:28px;
}
td{...}{
height:24px;
padding-left:1em;
}
</ style > 
< script type ="text/javascript" > ...
</ script >
</ head >
< body >
< h1 > 登录用户列表 </ h1 >
< table width ="80%" align ="center" >
< tr >
< th > 用户 </ th >< th > 登录时间 </ th >
</ tr >
< c:forEach items ="${stat}" var ="item" >
< tr >
< td > ${item[0]} </ td >< td > ${item[1]} </ td >
</ tr >
</ c:forEach >
</ table >
</ body >
</ html > 
/*************************************************************/
/*
wallimn原创文章,欢迎转载,转载请保留本文信息。
博客:http://blog.csdn.net/wallimn 邮件:wallimn@sohu.com
*/
/*************************************************************/