当 web 应用在 web 容器中运行时,web 应用内部会不断地发生各种事件:如 web 应用启动、web 应用停止,用户 session 开始、用户 session 结束、用户请求到达等。

实际上,Servlet API 提供了大量监听器来监听 web 应用的内部事件,从而允许当 web 内部事件发生时回调事件监听器内的方法。

使用 Listener 只需两个步骤:

1、定义 Listener 实现类

2、通过注解或者 web.xml 文件中配置 Listener

实现 Listener 类

与 AWT 事件编程完全相似,监听不同 web 事件的监听器也不相同。常用的 web 事件监听器接口有如下几个:

ServletContextListener:用于监听 web 应用的启动和关闭。

ServletContextAttributeListener:用于监听 ServletContext 范围(application)内属性的改变。

ServletRequestListener:用于监听用户请求。

ServletRequestAttributeListener:用于监听 ServletRequest 范围(request)内属性的改变。

HttpSessionListener:用于监听用户 session 的开始和结束。

HttpSessionAttributeListener:用于监听 HttpSession 范围(session)内属性的改变。

下面先以 ServletContextListener 为例来介绍 Listener 的开发和使用,ServletContextListener 用于监听 Web 应用的启动和关闭。该 Listener 类必须实现 ServletContextListener 接口,该接口包含如下两个方法:

contextInitialized(ServletContextEvent sce):启动 web 应用时,系统调用 Listener 的该方法。

contextDestroyed(ServletContextEvent sce):关闭 web 应用时,系统调用 Listener 的该方法。

下面创建一个获取数据库连接的 Listener,该 Listener 会在应用启动时获取数据库连接,并将获取到的连接设置成 application 范围内的属性。

GetConnectionListener.java

package com.baiguiren;

import java.sql.*;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.annotation.*; @WebListener
public class GetConnectionListener implements ServletContextListener
{
// 应用启动时,该方法被调用
public void contextInitialized(ServletContextEvent sce)
{
try {
// 取得该应用的 ServletContext 实例
ServletContext application = sce.getServletContext();
// 从配置参数中获取驱动
String driver = application.getInitParameter("driver");
// 从配置参数中获取数据库 url
String url = application.getInitParameter("url");
// 从配置参数中获取数据库用户名
String user = application.getInitParameter("user");
// 从配置参数中获取密码
String pass = application.getInitParameter("pass"); // 注册驱动
Class.forName(driver);
// 获取数据库连接
Connection connection = DriverManager.getConnection(url, user, pass);
// 将数据库连接设置成 application 范围内的属性
application.setAttribute("connection", connection);
} catch (Exception ex) {
System.out.println("Listener 中获取数据库连接出现异常:" + ex.getMessage());
}
} // 应用关闭时,该方法被调用
public void contextDestroyed(ServletContextEvent sce)
{
// 取得该应用的 ServletContext 实例
ServletContext application = sce.getServletContext();
Connection connection = (Connection)application.getAttribute("connection");
// 关闭数据库连接
if (connection != null) {
try {
connection.close();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}

  

配置 Listener

配置 Listener 只要向 Web 应用注册 Listener 实现类即可,无需配置参数之类的东西,因此十分简单。为 web 应用配置 Listener 也有两种方式。

1、使用 @WebListener 注解修饰 Listener 实现类

2、在 web.xml 文档中使用 <listener .../> 元素进行配置

使用 @WebListener 时通常无需指定任何属性,只要使用该注解修饰 Listener 实现类即可向 Web 应用注册该监听器。

在 web.xml 中使用 <listener .../> 元素进行配置时只要配置如下子元素即可。

listener-class:指定 Listener 实现类

<listener>
<!-- 指定 Listener 的实现类 -->
<listener-class>com.baiguiren.GetConnectionListener</listener-class>
</listener>

  

上面的配置片段向 web 应用注册了一个 Listener,其实现类为 com.baiguiren.GetConnectionListener。当 web 应用被启动时,该 Listener 的 contextInitialized 方法被触发,该方法会获取一个 JDBC Connection,并放入 application 范围内,这样所有的 JSP 页面都可通过 application 获取数据库连接,从而非常方便地进行数据库访问。

上例中所有页面使用的都是同一个连接,较为实用的方法是:应用启动时将一个数据源(javax.sql.DataSource 实例)设置成 application 属性,而所有 JSP 页面都通过 DataSource 实例来取得数据库连接,再进行数据库访问。

使用 ServletContextAttributeListener

ServletContextAttribtuteListener 用于监听 ServletContext(application)范围内属性的变化,实现该接口的监听器需要实现如下三个方法。

attributeAdded(ServletContextAttributeEvent event):当程序把一个属性存入 application 范围时触发该方法。

attributeRemoved(ServletContextAttributeEvent event):当程序把一个属性从 application 范围删除时触发该方法。

attributeReplaced(ServletContextAttributeEvent event):当程序替换 application 范围内的属性时将触发该方法。

MyServletContextAttributeListener.java

package com.baiguiren;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.annotation.*; @WebListener
public class MyServletContextAttributeListener implements ServletContextAttributeListener
{
// 当程序向 application 范围添加属性时触发
public void attributeAdded(ServletContextAttributeEvent event)
{
ServletContext application = event.getServletContext(); // 获取添加的属性名和属性值
String name = event.getName();
Object value = event.getValue();
System.out.println(application + "范围内添加了名为" + name + " , 值为" + value + "的属性!");
} // 当程序从 application 范围删除属性时触发该方法
public void attributeRemoved(ServletContextAttributeEvent event)
{
ServletContext application = event.getServletContext(); // 获取被删除的属性名和属性值
String name = event.getName();
Object value = event.getValue();
System.out.println(application + "范围内名为" + name + " , 值为" + value + "的属性被删除了!");
} // 当 application 范围的属性被替换时触发该方法
public void attributeReplaced(ServletContextAttributeEvent event)
{
ServletContext application = event.getServletContext(); // 获取被替换的属性名和属性值
String name = event.getName();
Object value = event.getValue();
System.out.println(application + "范围内名为" + name + " , 值为" + value + "的属性被替换了!");
}
}

  

使用 ServletRequestListener 和 ServletRequestAttributeListener

ServletRequestListener 用于监听用户请求的到达,实现该接口的监听器需要实现如下两个方法。

1、requestInitialized(ServletRequestEvent event):用户请求到达、被初始化时触发该方法。

2、requestDestroyed(ServletRequestEvent event):用户请求结束、被销毁时触发该方法。

ServletRequestAttributeListener 则用于监听 ServletRequest(request)范围内属性的变化,实现该接口的监听器需要实现 attributeAdded、attributeRemoved、attributeReplaced 三个方法。由此可见,ServletRequestAttributeListener 与 ServletContextAttributeListener 的作用相似,都用于监听属性的改变,只是 ServletRequestAttributeListener 监听 request 范围内属性的改变,ServletContextAttributeListener 监听的是 application 内属性的改变。

一个 Listener 可以监听多种事件,只要让该 Listener 实现多个接口就可以了:

RequestListener.java

package com.baiguiren;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.annotation.*; @WebListener
public class RequestListener implements ServletRequestListener, ServletRequestAttributeListener
{
// 用户请求到达、被初始化时触发该方法
public void requestInitialized(ServletRequestEvent event)
{
HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
System.out.println("----发向" + request.getRequestURI() + "请求被初始化----");
} // 当用户请求结束、被销毁时触发该方法
public void requestDestroyed(ServletRequestEvent event)
{
HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
System.out.println("----发向" + request.getRequestURI() + "请求被销毁----");
} // 当程序向 request 范围内添加属性时触发该方法
public void attributeAdded(ServletRequestAttributeEvent event)
{
ServletRequest request = event.getServletRequest();
// 获取添加的属性名和属性值
String name = event.getName();
Object value = event.getValue();
System.out.println(request + "范围内添加了名为" + name + " , 值为" + value + "的属性!");
} // 当从 request 范围内删除属性时触发该方法
public void attributeRemoved(ServletRequestAttributeEvent event)
{
ServletRequest request = event.getServletRequest();
// 获取被删除的属性名和属性值
String name = event.getName();
Object value = event.getValue();
System.out.println(request + "范围内名为" + name + " , 值为" + value + "的属性被删除了!");
} // 当 request 范围内的属性被替换时触发该方法
public void attributeReplaced(ServletRequestAttributeEvent event)
{
ServletRequest request = event.getServletRequest();
// 获取被替换的属性名和属性值
String name = event.getName();
Object value = event.getValue();
System.out.println(request + "范围内名为" + name + " , 值为" + value + "的属性被替换了!");
}
}

  

使用 HttpSessionListener 和 HttpSessionnattributeListener

HttpSessionListener 用于监听用户 session 的创建和销毁,实现该接口的监听器需要实现如下两个方法。

sessionCreated(HttpSessionEvent se):用户与服务器的会话开始、创建时触发该方法。

sessionDestroyed(HttpSessionEvent se):用户与服务器的会话断开、销毁时触发该方法。

HttpSessionAttributeListener 则用于监听 HttpSession(session)范围内属性的变化,实现该接口的监听器需要实现 attributeAdded、attributeRemoved、attributeReplaced 三个方法。由此可见,HttpSessionAttributeListener 与 ServletContextAttributeListener 的作用相似,都用于监听属性的改变,只是 HttpSessionAttributeListener 监听 session 范围内属性的改变,而 ServletContextAttributeListener 监听的是 application 范围内属性的改变。

实现 HttpSessionListener 接口的监听器可以监听每个用户会话的开始和断开,因此应用可以通过该监听器监听系统的在线用户。

羡慕是该监听器的实现类:

OnlineListener.java

package com.baiguiren;

import java.util.Hashtable;
import java.util.Map; import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.annotation.*; @WebListener
public class OnlineListener implements HttpSessionListener
{
// 当用户与服务器之间开始 session 时触发该方法
public void sessionCreated(HttpSessionEvent event)
{
HttpSession session = event.getSession();
ServletContext application = session.getServletContext();
// 获取 session ID
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 = new Hashtable<String, String>();
} // 将用户在线信息放入 Map 中
online.put(sessionID, user);
application.setAttribute("online", online);
}
} // 当用户与服务器之间 session 断开时触发该方法
public void sessionDestroyed(HttpSessionEvent event)
{
HttpSession session = event.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);
}
}

  

online.jsp

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.Map" %> <html>
<head>
<title>用户在线信息</title>
</head>
<body>
在线用户:
<table border="1" width="400">
<%
Map<String, String> online = (Map<String, String>)application.getAttribute("online");
if (online != null) {
for (String sessionId : online.keySet())
{
%>
<tr>
<td><%=sessionId%></td>
<td><%=online.get(sessionId)%></td>
</tr>
<%
}
}
%>
</table>
</body>
</html>

  

上面即使设置了 session ,实际结果都是 "游客",不知道为何。上面的 sessionCreated 里面感觉这样不太妥,因为 created 的时候理论上 session 里面还没有数据。

上面的统计在线用户的做法比较粗糙,我们可以通过 RequestListener 来更精准监控。

1、定义一个 ServletRequestListener,这个监听器负责监听每个用户请求,当用户请求到达时,系统将用户请求的 session ID、用户名、用户 IP、正在访问的资源、访问的时间记录下来。

2、启动一条后台线程,这条后台线程每隔一段时间检查上面的每条在线记录,如果某条在线记录的访问时间与当前时间相差超过了指定值,将这条记录删除即可。这条线程随着 web 应用启动而启动,可考虑使用 ServletContextListener 来完成。

AnotherRequestListener.java

package com.baiguiren;

import java.sql.ResultSet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*; import org.omg.PortableServer.REQUEST_PROCESSING_POLICY_ID; import javax.servlet.annotation.*; @WebListener
public class AnotherRequestListener implements ServletRequestListener
{
// 用户请求到达、被初始化时触发该方法
public void requestInitialized(ServletRequestEvent event)
{
HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
HttpSession session = request.getSession();
// 获取 session ID
String sessionId = session.getId();
// 获取访问的 IP 和正在访问的页面
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/jsp", "root", "root");
ResultSet rs = dd.query("select * from online_inf where session_id=?", true, sessionId);
// 如果该用户对应的 session ID 存在,表明是旧的回话
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 event)
{
HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
}
}

  

AnotherOnlineListener.java

package com.baiguiren;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.ResultSet;
import java.util.Hashtable;
import java.util.Map; import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*; import javax.servlet.annotation.*; @WebListener
public class AnotherOnlineListener implements ServletContextListener
{
// 超过该时间(10分钟)没有访问本站即认为用户已经离线
public final int MAX_MILLS = 10 * 60 * 1000; // 应用启动时触发该方法
public void contextInitialized(ServletContextEvent sce)
{
// 每 5 秒检查一次
new javax.swing.Timer(1000 * 5, new ActionListener(){ @Override
public void actionPerformed(ActionEvent e) {
try {
DbDao dd = new DbDao("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/online_inf", "root", "root");
ResultSet rs = dd.query("select * from online_inf", false);
StringBuffer beRemove = new StringBuffer("(");
while (rs.next()) {
// 如果距离上次访问时间超过了指定时间
if ((System.currentTimeMillis() - rs.getLong(5)) > MAX_MILLS) {
// 将需要被删除的 session ID 添加进来
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.closeConnection();
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
} public void contextDestroyed(ServletContextEvent sce) {}
}

  

online1.jsp

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.Map" %> <html>
<head>
<title>用户在线信息</title>
</head>
<body>
在线用户:
<table border="1" width="640"> <%
DbDao dd = new DbDao("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/online_inf", "root", "root");
// 查询 online_inf 表(在线用户表)的全部记录
ResultSet rs = dd.query("select * from online_inf", false);
while(rs.next*()) {
%> <tr>
<td><%=rs.getString(1)%></td>
<td><%=rs.getString(2)%></td>
<td><%=rs.getString(3)%></td>
<td><%=rs.getString(4)%></td>
</tr> <%
}
%>
</table>
</body>
</html>

  

Listener 介绍的更多相关文章

  1. listener介绍

    当Web 应用在Web 容器中运行时, Web 应用内部会不断地发生各种事件: 如Web 应用被启动.Web 应用被停止,用户session 开始.用户session 结束.用户请求到达等, 通常来说 ...

  2. Servlet的Listener介绍

    当Web应用在Web容器中运行时,Web应用内部会不断地发生各种事件:如Web应用被启动.Web应用被停止.用户session开始.用户session结束等.通常这些Web操作对开发者是透明的.但Se ...

  3. Quartz.net 3.x使用总结(一)——入门介绍

    1.Quartz.net简介 Quartz.NET是一个强大.开源.轻量级的任务调度框架.任务调度在我们的开发中经常遇到,如说:每天晚上三点让程序或网站执行某些代码,或者每隔5秒种执行一个方法等.Wi ...

  4. javaweb(4)之Listener&Filter

    监听器 (Listener) 介绍 监听器用于监听 web 应用中某些对象.信息的创建.销毁.增加,修改,删除等动作的发生,然后作出相应的响应处理.当范围对象的状态发生变化的时候,服务器自动调用监听器 ...

  5. 初学Listener

    一. Listener 介绍 Servlet API提供了大量监听器来监听web应用的的内部事件,从而允许当web内部事件发生时回调事件监听器内的方法. 使用listener分为两步 定义LIsten ...

  6. java web工程web.xml介绍

    转载自:http://blog.csdn.net/believejava/article/details/43229361 Web.xml详解: 1.web.xml加载过程(步骤) 首先简单讲一下,w ...

  7. Web.xml详解(转)

    这篇文章主要是综合网上关于web.xml的一些介绍,希望对大家有所帮助,也欢迎大家一起讨论. ---题记 一.            Web.xml详解: (一)  web.xml加载过程(步骤) 首 ...

  8. 轻量级Java EE企业应用实战(第4版):Struts 2+Spring 4+Hibernate整合开发(含CD光盘1张)

    轻量级Java EE企业应用实战(第4版):Struts 2+Spring 4+Hibernate整合开发(含CD光盘1张)(国家级奖项获奖作品升级版,四版累计印刷27次发行量超10万册的轻量级Jav ...

  9. JSP/Servlet基础语法

    相关学习资料 http://my.oschina.net/chape/blog/170247 http://docs.oracle.com/cd/E13222_01/wls/docs81/webapp ...

随机推荐

  1. 【python】详解time模块功能asctime、localtime、mktime、sleep、strptime、strftime、time等函数以及时间的加减运算

    在Python中,与时间处理相关的模块有:time.datetime以及calendar.学会计算时间,对程序的调优非常重要,可以在程序中狂打时间戳,来具体判断程序中哪一块耗时最多,从而找到程序调优的 ...

  2. MSCOCO - COCO API 的安装

    在 Windows 下安装 COCO API 的方法. 使用 pip 命令进行安装: pip install git+https://github.com/philferriere/cocoapi.g ...

  3. Pearson Distance

    Pearson Distance: where: 1.  is the covariance 2.  is the standard deviation of 3.  is the standard ...

  4. 用 Python 3 的 async / await 做异步编程

    前年我曾写过一篇<初探 Python 3 的异步 IO 编程>,当时只是初步接触了一下 yield from 语法和 asyncio 标准库.前些日子我在 V2EX 看到一篇<为什么 ...

  5. spark的数据结构 RDD——DataFrame——DataSet区别

    转载自:http://blog.csdn.net/wo334499/article/details/51689549 RDD 优点: 编译时类型安全 编译时就能检查出类型错误 面向对象的编程风格 直接 ...

  6. python_MySQL 数据库操作

    Python中的mysql操作可以使用MySQLdb模块来完成.它符合Python社区设计的Python Database API SpecificationV2.0标准,所以与其他的数据库操作的AP ...

  7. Scrum立会报告+燃尽图(Beta阶段第四次)

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2386 项目地址:https://coding.net/u/wuyy694 ...

  8. Alpha冲刺——第七天

    Alpha第七天 听说 031502543 周龙荣(队长) 031502615 李家鹏 031502632 伍晨薇 031502637 张柽 031502639 郑秦 1.前言 任务分配是VV.ZQ. ...

  9. 栈和队列在python中的实现

    栈和队列是两种基本的数据结构,同为容器类型.两者根本的区别在于: stack:后进先出 queue:先进先出 PS:stack和queue是不能通过查询具体某一个位置的元素而进行操作的.但是他们的排列 ...

  10. lintcode-203-线段树的修改

    203-线段树的修改 对于一棵 最大线段树, 每个节点包含一个额外的 max 属性,用于存储该节点所代表区间的最大值. 设计一个 modify 的方法,接受三个参数 root. index 和 val ...