2.10.5 使用HttpSessionListener和HttpSessionAttributeListener
HttpSessionListener
HttpSessionListener
用于监听用户session
的创建和销毁,实现该接口的监听器需要实现如下两个方法。
sessionCreated(HttpsessionEvent se)
:用户与服务器的会话开始、创建时触发该方法
sessionDestroyed(HttpsessionEvent se)
:用户与服务器的会话断开、销毁时触发该方法。
HttpSessionAttributeListener
HttpSessionAttributeListener
则用于监听HttpSession
(session
内置对象)范围内属性的变化,实现该接口的监听器需要实现
attributeAdded()
、
attributeRemoved()
、
attributeReplaced()
这三个方法。
由此可见,HttpSessionAttributeListener
与ServletContextAttributeListener
的作用相似,都用于监听属性的改变,只是HttpSessionAttributeListener
监听session
范围内属性的改变,
而ServletContextAttributeListener
监听的是application
范围内属性的改变。
程序 监听系统在线用户
实现HttpSessionListener
接口的监听器可以监听每个用户会话的开始和断开,因此应用可以通过该监听器监听系统的在线用户。
OnlineListener.java
下面是该监听器的实现类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package lee;
import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; import java.util.*;
@WebListener public class OnlineListener implements HttpSessionListener { public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); ServletContext application = session.getServletContext(); String sessionId = session.getId(); if (session.isNew()) { String user = (String) session.getAttribute("user"); user = (user == null) ? "游客" : user; Map<String, String> online = (Map<String, String>) application.getAttribute("online"); if (online == null) { online = Collections.synchronizedMap(new HashMap<String, String>()); } online.put(sessionId, user); application.setAttribute("online", online); } }
public void sessionDestroyed(HttpSessionEvent se) { HttpSession session = se.getSession(); ServletContext application = session.getServletContext(); String sessionId = session.getId(); Map<String, String> online = (Map<String, String>) application.getAttribute("online"); if (online != null) { online.remove(sessionId); } application.setAttribute("online", online); } }
|
上面的监听器实现类实现了HttpSessionListener
接口,该监听器可用于监听用户与服务器之间session
的开始、关闭.
- 当用户与服务器之间的
session
开始时,如果该session
是一次新的session
,程序就将当前用户的sessionId
、用户名存入application
范围的Map
中;
- 当用户与服务器之间的
session
关闭时,程序从application
范围的Map
中删除该用户的信息。
通过上面的方式,application
范围内的Map
就记录了当前应用的所有在线用户。
online.jsp
显示在线用户的页面代码很简单,只要迭代输出application
范围的Map
即可,如以下代码所
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <%@ page import="java.util.*" %> <!DOCTYPE html> <html> <head> <title> 用户在线信息 </title> </head> <body> 在线用户: <table width="400" border="1"> <% Map<String , String> online = (Map<String , String>)application .getAttribute("online"); for (String sessionId : online.keySet()) {%> <tr> <td><%=sessionId%> <td><%=online.get(sessionId)%> </tr> <%}%> </body> </html>
|
如果在本机启动三个不同的浏览器来模拟三个用户访问该应用,访问online.jsp
页面将看到如图2.42所示的页面效果。
程序 监听用户的详细信息
需要指出的是,采用HttpSessionListener
监听用户在线信息比较“粗糙”,只能监听到有多少人在线,每个用户的sessionId
等基本信息。如果应用需要监听到每个用户停留在哪个页面、本次在线的停留时间、用户的访问IP
等信息,则应该考虑定时检查HttpServletRequest
来实现。
通过检查HttpservletRequest
的做法可以更精确地监控在线用户的状态,这种做法的思路是:
- 定义一个
ServletRequestListener
,这个监听器负责监听每个用户请求,当用户请求到达时,系统将用户请求的sessionID
、用户名、用户IP
、正在访问的资源、访问时间记录下来
- 启动一条后台线程,这条后台线程每隔一段时间检查上面的每条在线记录,如果某条在线记录的访问时间与当前时间相差超过了指定值,将这条在线记录删除即可。这条后台线程应随着
Web
应用的启动而启动,可考虑使用ServletContextListener
来完成。
RequestListener.java
下面先定义一个ServletRequestListener
,它负责监听每次用户请求:
每次用户请求到达时,
- 如果是新的用户会话,将相关信息插入数据表:
- 如果是老的用户会话,则更新数据表中已有的在线记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package lee;
import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.sql.*;
@WebListener public class RequestListener implements ServletRequestListener { public void requestInitialized(ServletRequestEvent sre) { HttpServletRequest request = (HttpServletRequest) sre.getServletRequest(); HttpSession session = request.getSession(); String sessionId = session.getId(); String ip = request.getRemoteAddr(); String page = request.getRequestURI(); String user = (String) session.getAttribute("user"); user = (user == null) ? "游客" : user; try { DbDao dd = new DbDao("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/online_inf", "root", "32147"); ResultSet rs = dd.query("select * from online_inf where session_id=?", true, sessionId); if (rs.next()) { rs.updateString(4, page); rs.updateLong(5, System.currentTimeMillis()); rs.updateRow(); rs.close(); } else { dd.insert("insert into online_inf values(? , ? , ? , ? , ?)", sessionId, user, ip, page, System.currentTimeMillis()); } } catch (Exception ex) { ex.printStackTrace(); } }
public void requestDestroyed(ServletRequestEvent sre) { } }
|
上面的程序中粗体字代码控制用户会话是新的session
,还是已有的session
,新的session
将插入数据表;旧的session
将更新数据表中对应的记录。
接下来定义一个ServletContextListener
,它负责启动一条后台线程,这条后台线程将会定期检查在线记录,并删除那些长时间没有重新请求过的记录。
OnlineListener.java
该Listener
代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package lee;
import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; import java.sql.*; import java.awt.event.*;
@WebListener public class OnlineListener implements ServletContextListener { public final int MAX_MILLIS = 10 * 60 * 1000;
public void contextInitialized(ServletContextEvent sce) { new javax.swing.Timer(1000 * 5, new ActionListener() { public void actionPerformed(ActionEvent e) { try { DbDao dd = new DbDao("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/online_inf", "root", "32147"); ResultSet rs = dd.query("select * from online_inf", false); StringBuffer beRemove = new StringBuffer("("); while (rs.next()) { if ((System.currentTimeMillis() - rs.getLong(5)) > MAX_MILLIS) { beRemove.append("'"); beRemove.append(rs.getString(1)); beRemove.append("' , "); } } if (beRemove.length() > 3) { beRemove.setLength(beRemove.length() - 3); beRemove.append(")"); dd.modify("delete from online_inf where session_id in " + beRemove.toString()); } dd.closeConn(); } catch (Exception ex) { ex.printStackTrace(); } } }).start(); }
public void contextDestroyed(ServletContextEvent sce) { } }
|
上面的程序中粗体字代码负责收集系统中“超过指定时间未访问”的在线记录,然后程序通过一条SQL
语句删除这些在线记录。
需要指出的是,上面程序启动的后台线程定期检查的时间间隔为5秒,实际项目中这个时间应该适当加大,尤其是在线用户较多时,否则应用将会频繁地检查online_Inf
数据表中的全部记录,这将导致系统开销过大
online.jsp
显示在线用户的页面十分简单,只要查询online_Inf
表中全部记录,并将这些记录显示出来即可。
以下是该页面代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <%@ page import="java.sql.*,lee.*" %> <!DOCTYPE html> <html> <head> <title> 用户在线信息 </title> </head> <body> 在线用户: <table width="640" border="1"> <% DbDao dd = new DbDao("com.mysql.jdbc.Driver" , "jdbc:mysql://localhost:3306/online_inf" , "root" , "32147");
ResultSet rs = dd.query("select * from online_inf" , false); while (rs.next()) {%> <tr> <td><%=rs.getString(1)%> <td><%=rs.getString(2)%> <td><%=rs.getString(3)%> <td><%=rs.getString(4)%> </tr> <%}%> </body> </html>
|
启动不同浏览器访问该应用的不同页面,然后访问online.jsp
页面将可看到如图2.43所示的页面效果。
对于应用中所有需要统计在线用户页面,只要将上面的online.jsp
页面包含到页面中即可。
data.sql
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| DROP DATABASE IF EXISTS online_inf;
CREATE DATABASE online_inf;
USE online_inf;
CREATE TABLE online_inf ( session_id VARCHAR(255) PRIMARY KEY, username VARCHAR(255), user_ip VARCHAR(255), access_res VARCHAR(255), last_access BIGINT ); ;
|