监听器(Listener)在开发中的应用(一)
来源:互联网 发布:stc89c52单片机资料 编辑:程序博客网 时间:2024/04/25 12:57
监听器在JavaWeb开发中用得比较多,下面说一下监听器(Listener)在开发中的常见应用。
统计当前在线人数
在JavaWeb应用开发中,有时候我们需要统计当前在线的用户数,此时就可以使用监听器技术来实现这个功能了。
编写监听器,代码如下:
public class CountNumListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { ServletContext 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); } } @Override public void sessionDestroyed(HttpSessionEvent se) { ServletContext context = se.getSession().getServletContext(); Integer count = (Integer) context.getAttribute("count"); count--; context.setAttribute("count", count); }}
在web.xml文件中注册监听器。
<listener> <listener-class>cn.itcast.web.listener.example.CountNumListener</listener-class></listener>
我们写一个index.jsp页面来测试上面编写好的监听器。index.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 }人</body></html>
注意:统计出来的当前在线人数只是一个近似值。
自定义Session扫描器
当一个Web应用创建的Session很多时,为了避免Session占用太多的内存,我们可以选择手动将这些内存中的session销毁,那么此时也可以借助监听器技术来实现。
编写监听器
编写一个SessionScanner类,实现HttpSessionListener接口,去监听HttpSession对象。
为了管理服务器创建的session,就需要将其加到自己的一个容器里面去管理起来。即需要定义一个集合存储服务器创建的session。
这时选用ArrayList容器行不行呢?如下:
public class SessionScanner implements HttpSessionListener { private List<HttpSession> list = new ArrayList<HttpSession>(); @Override public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); list.add(session); } @Override public void sessionDestroyed(HttpSessionEvent se) { }}
答:显然不行。假设我选用这个容器,等一会儿我要管理这个容器里面的所有session,发现哪个session5分钟没人用了,就把这个session删除,那就涉及到对这个容器的大量增删,ArrayList底层是数组,性能不好,所以这个时候选用这个容器是不合适的,应选用底层为链表的LinkedList容器。
这时将SessionScanner监听器的代码修改为:
public class MySessionScanner implements HttpSessionListener { private List<HttpSession> list = new LinkedList<HttpSession>(); @Override public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); list.add(session); } @Override public 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集合内部的源代码可能类似于:
class MyList { Object[] arr = new Object[10]; public void add(Object obj) { // session if (arr[0] == null) { 第一个线程来了... 第二个线程来了... 这时出现两个线程抢一个位置的情况 arr[0] = obj; } }}
对于上述问题,解决办法为:
- 可以把这句代码
list.add(session);
放在同步代码块里面。 - 也可以把这个容器做成线程安全的。
我们选择第二种方法,即将一个集合做成线程安全的集合。可以使用 Collections.synchronizedList(List<T> list)
方法将不是线程安全的list集合包装线程安全的list集合。
此时SessionScanner监听器的代码修改为:
public class MySessionScanner implements HttpSessionListener { private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>()); @Override public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); list.add(session); } @Override public void sessionDestroyed(HttpSessionEvent se) { }}
为了实现某个session5分钟没人用了,就把这个session删除的需求,我们可以在web应用程序启动的时候就启动一个定时器,该定时器每隔5分钟执行一个任务,即扫描list集合,如果某个session5分钟没人用了,就把这个session删除。这时SessionScanner类需实现ServletContextListener接口,只要把启动定时器的代码写到contextInitialized(ServletContextEvent sce)方法里面,这个web应用一启动,定时器就启动了。
此时SessionScanner监听器的代码修改为:
public class MySessionScanner implements HttpSessionListener, ServletContextListener { private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>()); @Override public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); list.add(session); } @Override public void sessionDestroyed(HttpSessionEvent se) { } @Override public void contextInitialized(ServletContextEvent sce) { Timer timer = new Timer(); timer.schedule(new MyTask1(list), 0, 30*1000); // 定时器每隔30秒执行一个任务 } @Override public void contextDestroyed(ServletContextEvent sce) { }}//任务(每隔30秒干什么事情,即扫描list集合)class MyTask1 extends TimerTask { // 存储HttpSession的list集合 private List<HttpSession> list; public MyTask1(List<HttpSession> list) { this.list = list; } // run方法指明了任务要做的事情 @Override public void run() { Iterator<HttpSession> it = list.iterator(); while (it.hasNext()) { HttpSession session = it.next(); if ((System.currentTimeMillis() - session.getLastAccessedTime()) > 30*1000) { session.invalidate(); list.remove(session); // 从集合中移除摧毁的session。注意:在集合迭代的过程中,删除集合的元素,就会抛一个并发修改异常。 } } }}
提示:有关Timer类和TimerTask类怎么使用可以参考JDK API帮助文档。
如果程序像上面这样写,是不是万事大吉了呢?想得美啊!现在主要是MyTask1类有错误。现在我们来思考这样一个问题:在迭代过程中,准备添加或者删除元素。
public class Demo { public static void main(String[] args) { List list = new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); Iterator it = list.iterator(); while (it.hasNext()) { it.next(); list.add("ggg"); // java.util.ConcurrentModificationException } }}
运行以上程序,发现报异常:java.util.ConcurrentModificationException
(并发修改异常)。说明在集合迭代的过程中,删除集合的元素,就会抛一个并发修改异常,所以在迭代集合时,不可以通过集合对象的方法操作集合中的元素。如果想要在集合迭代的过程中,删除集合的元素,那该怎么办呢?这时就需要使用其子接口listIterator——List集合特有的迭代器(listIterator)。该接口只能通过List集合的listIterator()获取。
public class Demo4 { public static void main(String[] args) { List list = new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); ListIterator it = list.listIterator(); while (it.hasNext()) { it.next(); it.add("ggg"); } }}
此时SessionScanner监听器的代码修改为:
public class MySessionScanner implements HttpSessionListener, ServletContextListener { private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>()); @Override public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); list.add(session); } @Override public void sessionDestroyed(HttpSessionEvent se) { } @Override public void contextInitialized(ServletContextEvent sce) { Timer timer = new Timer(); timer.schedule(new MyTask1(list), 0, 30*1000); // 定时器每隔30秒执行一个任务 } @Override public void contextDestroyed(ServletContextEvent sce) { }}//任务(每隔30秒干什么事情,即扫描list集合)class MyTask1 extends TimerTask { // 存储HttpSession的list集合 private List<HttpSession> list; public MyTask1(List<HttpSession> list) { this.list = list; } // run方法指明了任务要做的事情 @Override public void run() { Iterator<HttpSession> it = list.listIterator(); while (it.hasNext()) { HttpSession session = it.next(); if ((System.currentTimeMillis() - session.getLastAccessedTime()) > 30*1000) { session.invalidate(); // 为了不抛并发修改异常 it.remove(); } } }}
如果程序这样写,并发布到网上去,别人来访问,只要访问的频率比较高,这时候又会出现并发线程安全异常。为什么呢?——在迭代list集合中的session的过程中可能有别的用户来访问,用户一访问,服务器就会为该用户创建一个session,此时就会调用sessionCreated往list集合中添加新的session,然而定时器在定时执行扫描遍历list集合中的session时是无法知道正在遍历的list集合又添加新的session进来了的,这样就导致了往list集合添加新的session和遍历list集合中的session这两个操作无法达到同步。那么解决的办法就是把
list.add(session)
和
Iterator<HttpSession> it = list.listIterator();while (it.hasNext()) { HttpSession session = it.next(); if ((System.currentTimeMillis() - session.getLastAccessedTime()) > 30*1000) { session.invalidate(); // 为了不抛并发修改异常 it.remove(); }}
这两段代码做成同步,保证当有一个线程在访问
list.add(session)
这段代码时,另一个线程就不能访问
Iterator<HttpSession> it = list.listIterator();while (it.hasNext()) { HttpSession session = it.next(); if ((System.currentTimeMillis() - session.getLastAccessedTime()) > 30*1000) { session.invalidate(); // 为了不抛并发修改异常 it.remove(); }}
这段代码。为了能够将这两段不相干的代码做成同步,只能定义一把锁(Object lock),然后给这两步操作加上同一把锁,用这把锁来保证往list集合添加新的session和遍历list集合中的session这两个操作达到同步,当在执行往list集合添加新的session操作时,就必须等添加完成之后才能够对list集合进行迭代操作,当在执行对list集合进行迭代操作时,那么必须等到迭代操作结束之后才能够往list集合添加新的session。
所以最终SessionScanner监听器的代码修改为:
public class MySessionScanner implements HttpSessionListener, ServletContextListener { private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>()); // 定义一个对象,让这个对象充当一把锁,用这把锁来保证往list集合添加新的session和遍历list集合中的session这两个操作达到同步 private Object lock = new Object(); // 在不同位置的代码怎么做到同步,可以利用一个对象锁就可实现 @Override public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); System.out.println(session + "被创建了!!!"); // 将该操作加锁进行锁定 synchronized (lock) { // 锁旗标 list.add(session); } } @Override public void sessionDestroyed(HttpSessionEvent se) { System.out.println(se.getSession() + "被销毁了!!!"); } @Override public void contextInitialized(ServletContextEvent sce) { Timer timer = new Timer(); timer.schedule(new MyTask1(list, lock), 0, 30*1000); // 定时器每隔30秒执行一个任务 } @Override public void contextDestroyed(ServletContextEvent sce) { }}//任务(每隔30秒干什么事情,即扫描list集合)class MyTask1 extends TimerTask { // 存储HttpSession的list集合 private List<HttpSession> list; // 存储传递过来的锁 private Object lock; public MyTask1(List<HttpSession> list, Object lock) { this.list = list; this.lock = lock; } // run方法指明了任务要做的事情 @Override public void run() { System.out.println("定时器执行!!!"); // 将该操作加锁进行锁定 synchronized (this.lock) { Iterator<HttpSession> it = list.listIterator(); while (it.hasNext()) { HttpSession session = it.next(); if ((System.currentTimeMillis() - session.getLastAccessedTime()) > 30*1000) { session.invalidate(); // 为了不抛并发修改异常 it.remove(); } } } }}
在web.xml文件中注册监听器
<listener> <listener-class>cn.itcast.web.listener.example.SessionScanner</listener-class></listener>
以上就是监听器的两个简单应用场景。
一道作业
写一个定时器,完成每天一到11:40就向用户发邮件的任务。
编写一个监听器,具体代码如下:
public class SendMailTimer implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { Timer timer = new Timer(); Calendar c = Calendar.getInstance(); c.set(2016, 8, 8, 18, 42, 55); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("发邮件!!!"); } }, c.getTime()); } @Override public void contextDestroyed(ServletContextEvent sce) { // TODO Auto-generated method stub }}
在web.xml文件中注册监听器
<listener> <listener-class>cn.itcast.web.listener.example.SendMailTimer</listener-class></listener>
运行结果就是:启动应用程序,到
2016-09-08 18:42:55
这一刻,在Eclipse的控制台打印一句话:发邮件!!!
。
要想你这个网站能定时执行某些任务的话,应该怎么做呢?——在实际开发里面,数据库里面有一张任务表,平时可以往任务表里面添加一些任务,整个应用程序启动的时候,定时器定时扫描这个任务表,只要扫描到你添加的一条任务,就用你这个任务new一个定时器出来执行。
- 监听器(Listener)在开发中的应用(一)
- 监听器(Listener)在开发中的应用
- 监听器(Listener)在开发中的应用
- 监听器(Listener)在开发中的应用(二)
- javaweb学习总结(四十七)——监听器(Listener)在开发中的应用
- javaweb学习总结(四十七)——监听器(Listener)在开发中的应用
- javaweb学习总结(四十七)——监听器(Listener)在开发中的应用
- javaweb学习总结(四十七)——监听器(Listener)在开发中的应用
- javaweb学习总结(四十七)——监听器(Listener)在开发中的应用
- javaweb学习总结(四十七)——监听器(Listener)在开发中的应用
- javaweb学习总结——监听器(Listener)在开发中的应用
- JavaWeb学习总结(四十七)——监听器(Listener)在开发中的应用
- java web学习总结47:监听器(Listener)在开发中的应用
- Listener与Filter在开发中的应用
- Servlet监听器在开发中的应用案例
- Servlet监听器在开发中的应用案例
- 监听器Listener应用举例
- Listener监听器一
- ios动态创建类Class
- qt for mac 工程文件
- 最难回答的20个科学大问题
- permutation相关问题
- maven安装和配置【windows系统】)
- 监听器(Listener)在开发中的应用(一)
- 自定义控件三部曲之动画篇(七)——ObjectAnimator基本使用
- 关于linux 内存分配
- 底部导航栏实现页面的切换(四):消息提示
- 关于android应用退出的问题
- asp.net+FusionCharts+jQuery(ajax)后台请求数据生成图表
- php 环境配置。
- 自定义控件三部曲之动画篇(八)——PropertyValuesHolder与Keyframe
- 权限系统无法登录,直接跳转到业务系统