监听器在JavaWeb开发中用得比较多,下面说一下监听器(Listener)在开发中的常见应用。

统计当前在线人数

在JavaWeb应用开发中,有时候我们需要统计当前在线的用户数,此时就可以使用监听器技术来实现这个功能了。
首先,编写一个监听器,代码如下:

package cn.liayun.web.listener.example;import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;public class CountNumListener implements HttpSessionListener {/*int count = 0;@Overridepublic void sessionCreated(HttpSessionEvent se) {count++;}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {count--;}*/@Overridepublic void sessionCreated(HttpSessionEvent se) {//得到ServletContextServletContext context = se.getSession().getServletContext();Integer count = (Integer) context.getAttribute("count");if (count == null) {count = 1;context.setAttribute("count", count);} else {count++;context.setAttribute("count", count);}}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {//得到ServletContextServletContext context = se.getSession().getServletContext();Integer count = (Integer) context.getAttribute("count");count--;context.setAttribute("count", count);}}

然后,在web.xml文件中注册该监听器。

<listener><listener-class>cn.liayun.web.listener.example.CountNumListener</listener-class>
</listener>

最后,我们写一个myindex.jsp页面来测试上面编写好的监听器。

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>统计当前在线用户人数</title>
</head>
<body>这是,网站首页!当前在线用户数(只是一个近似值,不是很准):${applicationScope.count }人<br/>
</body>
</html>

温馨提示:统计出来的当前在线人数只是一个近似值。

自定义Session扫描器

当一个Web应用创建的Session很多时,为了避免Session占用太多的内存,我们可以选择手动将这些内存中的Session销毁,那么此时也可以借助监听器技术来实现。

编写监听器

编写一个SessionScanner类,实现HttpSessionListener接口,去监听HttpSession对象。为了管理服务器创建的Session,就需要将其加到自己的一个容器里面去管理起来,即需要定义一个集合存储服务器创建的Session。这时选用ArrayList容器行不行呢?如下:

package cn.liayun.demo;import java.util.ArrayList;
import java.util.List;import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;public class SessionScanner implements HttpSessionListener {private List<HttpSession> list = new ArrayList<HttpSession>();@Overridepublic void sessionCreated(HttpSessionEvent se) {HttpSession session = se.getSession();list.add(session);}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {}}

上面这样做,显然是不行的。假设我选用这个容器,等一会儿我要管理这个容器里面的所有Session,发现哪个Session如果5分钟没人用了,就把这个Session删除掉,那就涉及到对这个容器的大量增删操作,ArrayList底层是数组,性能不好,所以这个时候选用这个容器是不合适的,应选用底层为链表的LinkedList容器。这时将SessionScanner监听器的代码修改为:

package cn.liayun.demo;import java.util.LinkedList;
import java.util.List;import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;public class SessionScanner implements HttpSessionListener {private List<HttpSession> list = new LinkedList<HttpSession>();@Overridepublic void sessionCreated(HttpSessionEvent se) {HttpSession session = se.getSession();list.add(session);}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {}}

虽然是修改了,但是像上面那样写会涉及到线程安全问题,SessionScaner对象在内存中只有一个。sessionCreated方法可能会被多个人同时调用,当有多个人并发访问站点时,服务器同时为这些并发访问的人创建Session,那么sessionCreated方法在某一时刻内会被几个线程同时调用,几个线程并发调用sessionCreated方法。sessionCreated方法的内部处理是往一个集合中添加创建好的Session,那么在加Session的时候就会涉及到几个Session同时抢夺集合中一个位置的情况,所以往集合中添加Session时,一定要保证集合是线程安全的才行。
用我自己的话说就是:只要有一个Session创建了,就往list这个容器里面加,还会有线程并发访问的问题。sessionCreated方法里面这句代码list.add(session);有可能被多线程并发访问。若有2个哥们同时访问服务器,服务器会针对这2个哥们同时创建Session,那这句代码list.add(session);会同时被调用,那么这时就会有2个Session同时被加到容器里面去,就会出现2个Session在容器里面抢夺同一个位置的情况。又由于LinkedList集合不是线程安全的,现在有2个线程同时调用add方法往这个集合里面加东西,这时完全有可能出现2个东西抢夺一个位置的情况,就有可能出现有一个Session加到容器里面的第一个位置了,第二个Session就把第一个位置的Session覆盖掉了。LinkedList集合内部的源代码可能类似于:

对于上述问题,解决办法有两种:

  1. 可以把这句代码list.add(session);放在同步代码块里面;
  2. 也可以把这个容器做成线程安全的。

我们选择第二种方法,即将一个集合做成线程安全的集合。可以使用 Collections.synchronizedList(List<T> list)方法将不是线程安全的list集合包装成线程安全的list集合。此时SessionScanner监听器的代码应修改为:

package cn.liayun.demo;import java.util.Collections;
import java.util.LinkedList;
import java.util.List;import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;public class SessionScanner implements HttpSessionListener {/** 由于现在我们选用的集合是一个非线程安全的集合,那么集合的add方法就没有考虑到线程安全问题。* 如果有两个线程并发调用这个add方法的话,就会导致两个session抢一个位置的情况,线程安全问题就会发生,这样子是不行的。*/private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());//返回一个线程安全的集合@Overridepublic void sessionCreated(HttpSessionEvent se) {HttpSession session = se.getSession();list.add(session);}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {}}

为了实现某个Session5分钟没人用了,就把这个Session5删除掉的需求,我们可以在Web应用程序启动的时候就启动一个定时器,该定时器每隔5分钟执行一个任务,即扫描list集合,如果某个Session5分钟没人用了,就把这个Session删除掉。这时SessionScanner类需要实现ServletContextListener接口,只要把启动定时器的代码写到contextInitialized(ServletContextEvent sce)方法里面,这个Web应用一启动,定时器就启动了。此时,SessionScanner监听器的代码应修改为:

package cn.liayun.demo;import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;public class SessionScanner implements HttpSessionListener, ServletContextListener {/** 由于现在我们选用的集合是一个非线程安全的集合,那么集合的add方法就没有考虑到线程安全问题。* 如果有两个线程并发调用这个add方法的话,就会导致两个session抢一个位置的情况,线程安全问题就会发生,这样子是不行的。*/private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());//返回一个线程安全的集合@Overridepublic void sessionCreated(HttpSessionEvent se) {HttpSession session = se.getSession();list.add(session);}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {}@Overridepublic void contextInitialized(ServletContextEvent sce) {//对应着Web应用程序启动,Web应用程序启动时,new一个定时器(Timer)Timer timer = new Timer();timer.schedule(new MyTask(list), 0, 30 * 1000); //定时器每隔30秒执行一个任务}@Overridepublic void contextDestroyed(ServletContextEvent sce) {// TODO Auto-generated method stub}}//任务(每隔30秒干什么事情,即扫描list集合)
class MyTask extends TimerTask {//存储HttpSession的list集合private List<HttpSession> list;public MyTask(List<HttpSession> list) {this.list = list;}//run方法指定所要做的事情——每隔30秒扫描list集合@Overridepublic void run() {Iterator<HttpSession> it = list.iterator();    //5while (it.hasNext()) {HttpSession session = it.next();if ((System.currentTimeMillis() - session.getLastAccessedTime()) > 30 * 1000) {session.invalidate();//摧毁session//再从集合里面把已经摧毁的session移除掉list.remove(session);//温馨提示:如果集合在迭代的过程中,删除集合的元素,就会抛一个并发修改移异常。}}}
}

温馨提示:有关Timer类和TimerTask类怎么使用可以参考JDK API帮助文档。
如果程序像上面这样写,是不是万事大吉了呢?想得美啊!现在主要是MyTask类有错误。我们来思考这样一个问题:在迭代过程中,准备添加或者删除元素。

说明在集合迭代的过程中,删除集合的元素,就会抛一个并发修改异常,所以在迭代集合时,不可以通过集合对象的方法操作集合中的元素。如果想要在集合迭代的过程中,删除集合的元素,那该怎么办呢?这时就需要使用其子接口ListIterator——List集合特有的迭代器。注意,该接口只能通过List集合的listIterator()获取。

此时SessionScanner监听器的代码又要修改为:

package cn.liayun.demo;import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;public class SessionScanner implements HttpSessionListener, ServletContextListener {/** 由于现在我们选用的集合是一个非线程安全的集合,那么集合的add方法就没有考虑到线程安全问题。* 如果有两个线程并发调用这个add方法的话,就会导致两个session抢一个位置的情况,线程安全问题就会发生,这样子是不行的。*/private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());//返回一个线程安全的集合@Overridepublic void sessionCreated(HttpSessionEvent se) {HttpSession session = se.getSession();list.add(session);}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {}@Overridepublic void contextInitialized(ServletContextEvent sce) {//对应着Web应用程序启动,Web应用程序启动时,new一个定时器(Timer)Timer timer = new Timer();timer.schedule(new MyTask(list), 0, 30 * 1000); //定时器每隔30秒执行一个任务}@Overridepublic void contextDestroyed(ServletContextEvent sce) {// TODO Auto-generated method stub}}//任务(每隔30秒干什么事情,即扫描list集合)
class MyTask extends TimerTask {//存储HttpSession的list集合private List<HttpSession> list;public MyTask(List<HttpSession> list) {this.list = list;}//run方法指定所要做的事情——每隔30秒扫描list集合@Overridepublic void run() {ListIterator<HttpSession> it = list.listIterator();while (it.hasNext()) {HttpSession session = it.next();if ((System.currentTimeMillis() - session.getLastAccessedTime()) > 30 * 1000) {session.invalidate();//摧毁session//再从集合里面把已经摧毁的session移除掉//list.remove(session);//温馨提示:如果集合在迭代的过程中,删除集合的元素,就会抛一个并发修改异常。it.remove();//发现session没人用了,调用迭代器的方法移除掉}}}
}

如果程序这样写,并发布到网上去,别人来访问,只要访问的频率比较高,这时候又会出现并发线程安全异常。为什么呢?在迭代list集合中的Session的过程中可能有别的用户来访问,用户一访问,服务器就会为该用户创建一个Session,此时就会调用sessionCreated方法往list集合中添加新的Session,然而定时器在定时执行扫描遍历list集合中的Session时是无法知道正在遍历的list集合又添加新的Session进来了的,这样就导致了往list集合添加新的Session和遍历list集合中的Session这两个操作无法达到同步。那么解决的办法就是把下面这段代码:

list.add(session);

ListIterator<HttpSession> it = list.listIterator();while (it.hasNext()) {HttpSession session = it.next();if ((System.currentTimeMillis() - session.getLastAccessedTime()) > 30 * 1000) {session.invalidate();//摧毁session//再从集合里面把已经摧毁的session移除掉//list.remove(session);//温馨提示:如果集合在迭代的过程中,删除集合的元素,就会抛一个并发修改异常。it.remove();//发现session没人用了,调用迭代器的方法移除掉}
}

这段代码做成同步,保证当有一个线程在访问其中一段代码的时候,另一个线程就不能访问另外一段代码。
为了能够将这两段不相干的代码做成同步,只能定义一把锁(Object lock),然后给这两步操作加上同一把锁,用这把锁来保证往list集合添加新的Session和遍历list集合中的Session这两个操作达到同步,当在执行往list集合添加新的Session操作时,就必须等添加完成之后才能够对list集合进行迭代操作,当在执行对list集合进行迭代操作时,那么必须等到迭代操作结束之后才能够往list集合添加新的Session。所以,最终SessionScanner监听器的代码应为:

package cn.liayun.web.listener.example;import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;//SessionScanner这个类即是HttpSession监听器,又是ServletContext监听器
public class SessionScanner implements HttpSessionListener, ServletContextListener {/** 由于现在我们选用的集合是一个非线程安全的集合,那么集合的add方法就没有考虑到线程安全问题。* 如果有两个线程并发调用这个add方法的话,就会导致两个session抢一个位置的情况,线程安全问题就会发生,这样子是不行的。*/private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());//返回一个线程安全的集合//定义一个对象,让这个对象充当一把锁,用这把锁来保证往list集合添加新的session和遍历list集合中的session这两个操作达到同步private Object lock = new Object();//锁。温馨提示:在不同位置的代码怎么做到同步呢?可以利用一个对象锁就可实现。@Overridepublic void contextInitialized(ServletContextEvent sce) {//对应着Web应用程序启动,Web应用程序启动时,new一个定时器(Timer)Timer timer = new Timer();timer.schedule(new MyTask(list, lock), 0, 30 * 1000);//定时器每隔30秒执行一个任务}@Overridepublic void sessionCreated(HttpSessionEvent se) {HttpSession session = se.getSession();System.out.println(session + "被创建了!!!");//将该操作加锁进行锁定synchronized (lock) {//现在有一个线程进来了,它就拿到了这个对象的锁棋标list.add(session);}}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {System.out.println(se.getSession() + "被销毁了!!!");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {// TODO Auto-generated method stub}}/** 但是这个程序代码如果这样写,我如果发布到网上去的话,别人来访问的话,只要访问的频率比较高,这时又会出现异常。* 这时还是并发线程安全异常,这个时候为什么又会出现线程安全异常呢?大家来思考一个问题,这个问题很隐晦。* 现在我这个定时器每隔1分钟就执行这里面的任务,现在有一个定时器正在执行这里面的任务,正在迭代的过程中,* 这时有可能有一个哥们来登录网站,就会为他创建一个session,list.add(session);这句代码就会执行,这句代码* 就会往List集合里面加东西,这时候往List集合里面加东西了,这时迭代器并不知道,它依然按照原来的方式进行迭代,* 又会抛出并发访问异常。* * 我想避免这个异常,那么该怎么办呢?那就需要把这段迭代代码和list.add(session);这句代码放到一个同步代码块里面去。* 也就是说,现在有一个线程在访问迭代集合的代码,那么别的线程就不能访问list.add(session);这句代码。*///任务(每隔30秒干什么事情,即扫描list集合)
class MyTask extends TimerTask {//存储HttpSession的list集合private List<HttpSession> list;//存储传递过来的锁private Object lock;public MyTask(List<HttpSession> list, Object lock) {this.list = list;this.lock = lock;}//run方法指定所要做的事情——每隔30秒扫描list集合@Overridepublic void run() {System.out.println("定时器执行!!");//将该操作加锁进行锁定synchronized (this.lock) {ListIterator<HttpSession> it = list.listIterator();while (it.hasNext()) {HttpSession session = it.next();if ((System.currentTimeMillis() - session.getLastAccessedTime()) > 30 * 1000) {session.invalidate();//摧毁session//再从集合里面把已经摧毁的session移除掉//list.remove(session);//温馨提示:如果集合在迭代的过程中,删除集合的元素,就会抛一个并发修改异常。it.remove();//发现session没人用了,调用迭代器的方法移除掉}}}}}

在web.xml文件中注册监听器

<listener><listener-class>cn.liayun.web.listener.example.SessionScanner</listener-class>
</listener>

以上就是监听器的两个简单应用场景。

一道作业

需求:写一个定时器,完成每天一到18:30就向用户发邮件的任务。

首先,编写一个监听器,具体代码如下:

package cn.liayun.web.listener.example;import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;public class SendMailTimer implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent sce) {Timer timer = new Timer();Calendar c = Calendar.getInstance();c.set(2019, 4, 30, 18, 30, 0);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("发邮件");}}, c.getTime());}@Overridepublic void contextDestroyed(ServletContextEvent sce) {// TODO Auto-generated method stub}}

然后,在web.xml文件中注册监听器。

<listener><listener-class>cn.liayun.web.listener.example.SendMailTimer</listener-class>
</listener>

运行结果就是:启动应用程序,到2019-05-30 18:30:00这一刻,将在Eclipse的控制台打印一句话:发邮件!!!。
要想你这个网站能定时执行某些任务的话,应该怎么做呢?在实际开发里面,数据库里面有一张任务表,平时可以往任务表里面添加一些任务,整个应用程序启动的时候,定时器定时扫描这个任务表,只要扫描到你添加的一条任务,就用你这个任务new一个定时器出来执行。

Java Web基础入门第八十二讲 Listener(监听器)——监听器在开发中的应用(一)相关推荐

  1. Java Web基础入门第八讲 Java Web开发入门——初始WEB服务器

    WEB开发的相关知识 WEB,在英语中web即表示网页的意思,它用于表示Internet主机上供外界访问的资源.Internet上供外界访问的Web资源分为: 静态web资源(如html页面):指we ...

  2. Web基础配置篇(十二): Elasticsearch的安装配置及入门使用

    Web基础配置篇(十二): Elasticsearch的安装配置及入门使用 一.概述 ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RE ...

  3. 【genius_platform软件平台开发】第八十二讲:ARM Neon指令集一(ARM NEON Intrinsics, SIMD运算, 优化心得)

    1. ARM Neon Intrinsics 编程 1.入门:基本能上手写Intrinsics 1.1 Neon介绍.简明案例与编程惯例 1.2 如何检索Intrinsics 1.3 优化效果案例 1 ...

  4. 五十二、微信小程序云开发中的云存储

    @Author:Runsen 暑假很长,明年就是找工作的时候了.这个时候必须比之前还要拼命. 百翻无下,努力就是我的代言词.今天,正式进入云存储的学习.云存储这个概念在之前学习的时候没有注意到. 下面 ...

  5. Android零基础入门第38节:初识Adapter

    2019独角兽企业重金招聘Python工程师标准>>> 在上一节一起了解了ListView的简单使用,那么本节继续来学习与ListView有着千丝万缕的Adapter. 一.了解MV ...

  6. Android零基础入门第40节:自定义ArrayAdapter

    原文:Android零基础入门第40节:自定义ArrayAdapter ListView用起来还是比较简单的,也是Android应用程序中最重要的一个组件,但其他ListView可以随你所愿,能够完成 ...

  7. Android零基础入门第20节:CheckBox和RadioButton使用大全

    原文:Android零基础入门第20节:CheckBox和RadioButton使用大全 本期先来学习Button的两个子控件,无论是单选还是复选,在实际开发中都是使用的较多的控件,相信通过本期的学习 ...

  8. Android零基础入门第24节:自定义View简单使用

    Android零基础入门第24节:自定义View简单使用 原文:Android零基础入门第24节:自定义View简单使用 当我们开发中遇到Android原生的组件无法满足需求时,这时候就应该自定义Vi ...

  9. Android零基础入门第33节:Android事件处理概述

    原文:Android零基础入门第33节:Android事件处理概述 通过对Android基本组件的学习,也有接触少部分Android的事件处理,比如按钮的点击事件.选框的状态切换事件. 一.Andro ...

最新文章

  1. MyEclipse搭建java Web项目开发环境
  2. python **运算符及多参数传参
  3. 《剑指offer》删除链表中重复的节点
  4. Python贪婪算法
  5. [POJ 1330] Nearest Common Ancestors (倍增法)
  6. 第一次失效_特斯拉螺栓腐蚀失效分析_搜狐汽车
  7. mysql 5.6 安装 中文_ubuntu 下mysql 5.6安装、删除和配置中文乱码问题
  8. GO语言的进阶之路-Golang高级数据结构定义
  9. java 中的事物怎么配置_java – 在hibernate中如何以编程方式设置事务的隔离级别,或者如何创建具有不同隔离级别的两个事务...
  10. nodejs学习—安装
  11. JDY-1110B电压继电器
  12. 海信电视老出现android是什么意思,海信电视屏幕上显示“智能电视系统启动中,请稍后”是什么意思?怎样处理?- 一起装修网...
  13. 用什么材料作电磁屏蔽呢?
  14. 使用SQL语句在K3里进行反结帐- -
  15. Nginx--流量限制(最有用的功能之一)
  16. 小米pro15拆机_小米笔记本Pro 15增强版值得买吗 小米笔记本Pro 15增强版拆解+评测...
  17. 求两个圆公切线的模板
  18. python连接sftp下载文件及文件夹
  19. PyCharm搜索技巧快捷键
  20. C# 进行 Starlink 仿真03:72轨道面 * 22颗卫星 F相位因子==11 的Walker星座,创建3168条星间链路,并与 icarus 论文的Python结果相对比。

热门文章

  1. 浅谈激光的单色性与相干性
  2. 【C语言基础练习】有红、绿、蓝三种颜色的球各3个。现在将着9个球混合放在一个盒子中,从中任意摸出6个,编程计算摸出球的各种颜色搭配。
  3. 虚拟摄像头驱动原理及开发
  4. 类和对象(一)——类对象概念及定义
  5. 微信小程序——crypto-js参数加密、解密问题
  6. 词袋模型和空间金字塔模型
  7. 学习通信原理之——从实验中理解频谱/功率谱/功率谱密度(MATLAB演示)
  8. 一本通 1184:明明的随机数 1187:统计字符数 提示:桶排 (爸爸)
  9. 【产品人卫朋】自媒体运营的5个阶段,以及增长策略
  10. DSP和MCU的区别