一、监听器原理
1、监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行.
2、监听器典型案例:监听window窗口的事件监听器
public class Demo1 { /** * 面试题:请描述一下java事件监听机制.(和jquery事件完全类似) * 1.java的事件监听机制涉及到三个组件:事件源、事件监听器、事件对象 * 2.当事件源上发生操作时,它将会调用事件监听器的一个方法,并在调用这个方法时,会传递事件对象过来. * 3.事件监听器由开发人员编写,开发人员在事件监听器中,通过事件对象可以拿到事件源,从而对事件源上进行处理. */ public static void main(String[] args) { Frame f = new Frame();//事件源 f.setSize(400, 400); f.setVisible(true); //这种传递实现接口的对象属于策略设计模式的应用 f.addWindowListener(new MyListener());//注册事件监听器 } } class MyListener implements WindowListener{ public void windowClosing(WindowEvent e) { Frame f = (Frame) e.getSource(); f.dispose(); } public void windowDeactivated(WindowEvent e) { } ... }
3、自己实现监听器
//观察者设计模式(通常用来处理事件系统) //事件源 class Person{ private PersonListener listener; public void registerListener(PersonListener listener){ this.listener = listener; } public void run(){ if(listener!=null){ Even even = new Even(this); this.listener.dorun(even); } System.out.println("runn!!"); } public void eat(){ if(listener!=null){ Even e = new Even(this); this.listener.doeat(e); } System.out.println("eat!!"); } } //事件监听器 interface PersonListener{ public void dorun(Even even); public void doeat(Even even); } //事件对象(封装事件源) class Even{ private Person person; public Even() { super(); } public Even(Person person) { super(); this.person = person; } public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } } public class Demo3 { public static void main(String[] args) { Person p = new Person(); p.registerListener(new MyListener1()); p.eat(); p.run(); } } class MyListener1 implements PersonListener{ public void doeat(Even even) { System.out.println(even.getPerson()+"你天天吃,你就知道吃,你猪啊!!"); } public void dorun(Even even) { System.out.println(even.getPerson()+"你吃完就跑,有病!!"); } }
4、一个面试题,多个客户在餐厅并发点餐,现在只有一个打印机,打印出单子交给厨房,系统该怎么做?
一种方法是每隔10秒就检查一次,检测到就打印,这种不好,有cpu空转问题,应该是用个监听器取监听这个装菜单的容器(这个容器应该同步),只要它一add就执行监听器方法打印菜单.
ps:观察者模式的一个例子:求职者先在猎头处注册,当有新的工作机会时猎头就会通知求职者.
二、 servlet监听器
1、servlet监听器分类
在Servlet规范中定义了多种类型的监听器,它们用于监听的事件源分别为 ServletContext, HttpSession 和 ServletRequest 这三个域对象.Servlet规范针对这三个对象上的操作,又把这多种类型的监听器划分为三种类型.
•监听三个域对象创建和销毁的事件监听器
•监听域对象中属性的增加和删除的事件监听器
•监听绑定到 HttpSession 域中的某个对象的状态的事件监听器.(查看API文档),也就是某个javabean对象可以监听自己是否被放入HttpSession域中(无需在web.xml中配置,用的不多).
2、编写 Servlet 监听器
1、ServletContextListener 接口用于监听 ServletContext 对象的创建和销毁事件.
当 ServletContext 对象被创建时,激发contextInitialized (ServletContextEvent sce)方法
当 ServletContext 对象被销毁时,激发contextDestroyed(ServletContextEvent sce)方法.
public class MyServletContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) { System.out.println("servletContext被创建了!"); } public void contextDestroyed(ServletContextEvent sce) { System.out.println("servletcontext被销毁了!!"); } }
//web.xml,这里web服务器通过反射创建MyServletContextListener对象然后注册给ServletContext
<listener> <listener-class>cn.itcast.web.listener.ServletContextListener</listener-class> </listener>
提问,servletContext域对象何时创建和销毁:
•创建:服务器启动针对每一个web应用创建servletcontext.
•销毁:服务器关闭前先关闭代表每一个web应用的servletContext.
ps:实际开发可以用来初始化配置文件,例如spring中这么使用.
2、HttpSessionListener接口用于监听HttpSession的创建和销毁.
提问,Session域对象何时创建和销毁:
•创建:用户每一次访问时,服务器创建session
•销毁:如果用户的session 30分钟没有使用,服务器就会销毁session,我们在web.xml里面也可以配置session失效时间
注意:
1>如果直接访问jsp页面会创建session,因为直接访问jsp会被web服务器转为Servlet,然后传给Servet八大隐式对象,直接访问Servlet不会创建session.
2>刷新页面不会重新创建session,基于一个浏览器弹出来的都共用一个session,关了浏览器session不会摧毁,驻留内存30分钟不用自动摧毁.
3>客户端禁用cookie,刷新页面会一直创建session,因为没有带sessionID过来,服务器会认为是一次新的回话.
ps:实际开发可以用来统计浏览器使用率,来访者ip等.
3、ServletRequestListener用于监听ServletRequest 对象的创建和销毁.
提问,ServletRequest域对象何时创建和销毁:
•创建:用户每一次访问,都会创建一个reqeust,sendRedirect会创建,forward不会.
•销毁:当前访问结束,request对象就会销毁
ps:实际开发可以用来统计网站点击量.
4、监听域对象中属性的增加和删除的事件监听器(用的不多)
1、这三个监听器接口分别是ServletContextAttributeListener, HttpSessionAttributeListener ServletRequestAttributeListener.这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同.
2、当向被监听器对象中增加一个属性时,web容器就调用事件监听器的 attributeAdded 方法进行响应,这个方法接受一个事件类型的参数,监听器可以通过这个参数来获得正在增加属性的域对象和被保存到域中的属性对象.
//ServletContextAttributeListener public class MyServletContextAttributeListener implements ServletContextAttributeListener { public void attributeAdded(ServletContextAttributeEvent scab) { String name = scab.getName(); Object value = scab.getValue(); System.out.println("向servletContext中存了:" + name + "=" + value); } public void attributeRemoved(ServletContextAttributeEvent scab) { System.out.println("从servletcontext中删除了:" + scab.getName()); } public void attributeReplaced(ServletContextAttributeEvent scab) { System.out.println("servletcontext中" + scab.getName() + "属性被替换了"); } } //HttpSessionAttributeListener,ServletRequestAttributeListener public class HttpSessionAndServletRequestAttributeListener implements HttpSessionAttributeListener, ServletRequestAttributeListener { public void attributeAdded(HttpSessionBindingEvent se) { System.out.println("向session中加入东西了!!"); } public void attributeRemoved(HttpSessionBindingEvent se) { System.out.println("从session中删了东西!!"); } public void attributeReplaced(HttpSessionBindingEvent se) { System.out.println("把session中的属性替换了!!"); } public void attributeAdded(ServletRequestAttributeEvent srae) { System.out.println("向request中加入东西了!!"); } public void attributeRemoved(ServletRequestAttributeEvent srae) { System.out.println("从request中删了东西!!"); } public void attributeReplaced(ServletRequestAttributeEvent srae) { System.out.println("把request中的属性替换了!!"); } }
三、Servlet监听器案例
1、统计网站当前在线用户
public class CountNumListener implements HttpSessionListener { public void sessionCreated(HttpSessionEvent se) { ServletContext context = se.getSession().getServletContext(); Integer count = (Integer) context.getAttribute("count"); if(count==null){ count = 1; }else{ count++; } context.setAttribute("count", count); } public void sessionDestroyed(HttpSessionEvent se) { ServletContext context = se.getSession().getServletContext(); Integer count = (Integer) context.getAttribute("count"); count--; context.setAttribute("count", count); } } //直接如下写法是不行的,无法在页面中取到,一般是显示在首页中. public class CountNumListener implements HttpSessionListener { int count; public void sessionCreated(HttpSessionEvent se) { count++; } public void sessionDestroyed(HttpSessionEvent se) { count--; } }
2、自定义session扫描器,自己来管理session.
//将session装入集合自己管理,定时器每隔一段时间执行一次,session超过一段时间未使用,则删除. public class SessionScanner implements HttpSessionListener,ServletContextListener { //Collections这个集合工具类生成的集合是线程安全的. private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>()); private Object lock = new Object(); public void contextInitialized(ServletContextEvent sce) { Timer timer = new Timer(); timer.schedule(new MyTask(list,lock), 0, 30*1000); } public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); System.out.println(session + "被创建了!!"); synchronized (lock) { //锁旗标 list.add(session); } } public void sessionDestroyed(HttpSessionEvent se) { System.out.println(se.getSession() + "被销毁了"); } public void contextDestroyed(ServletContextEvent sce) { } } class MyTask extends TimerTask{ private List list; private Object lock; public MyTask(List list,Object lock){ this.list = list; this.lock = lock; } @Override public void run() { System.out.println("定时器执行!!"); synchronized (this.lock) { ListIterator it = list.listIterator(); while(it.hasNext()){ HttpSession session = (HttpSession) it.next(); if((System.currentTimeMillis()-session.getLastAccessedTime())>30*1000){ session.invalidate(); //list.remove(session); //并发修改异常 it.remove(); } } } } }
ps:
class MyList{ Object arr[] = new Object[10]; public void add(Object obj){ //session if(arr[0]==null){ arr[0] = obj; } ... } }
如上是自定义的一个集合,显示多个线程共同调用add方法时存在并发问题的原因,在if(arr[0]==null)后cpu切换,会造成集合元素的覆盖,并发环境下这里应该使用线程安全的集合Collections.synchronizedList或vector(线程安全的Arraylist),vector现在已经不推荐使用,Collections有将各种集合变为线程安全集合的方法.这里有一个注意问题:
List list = Collections.synchronizedList(new ArrayList()); ... synchronized(list) { Iterator i = list.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); }
synchronizedList在迭代的时候,需要开发者自己加上线程锁控制代码,为什么呢
因为迭代器涉及的代码没有在java api中没有加上线程同步代码.
整个迭代的过程中如果在循环外面不加同步代码,在一次次迭代之间,其他线程对于这个容器的add或者remove会影响整个迭代的预期效果,所以这里需要用户在整个循环外面加上synchronized(list),这也是迭代中remove元素时出现并发访问异常的原因,感觉用for循环更好,就不用加锁了.
3、定时发邮件,现在一般使用线程池的定时任务代替传统的定时器.略
4、实现了HttpSessionActivationListener接口的 JavaBean 对象可以感知自己被活化和钝化的事件(用的不多)
//对象要写入硬盘必须序列化,实现Serializable 接口 public class MyBean implements HttpSessionActivationListener,Serializable { public void sessionDidActivate(HttpSessionEvent se) { System.out.println("javabean随着session从硬盘回到内存了!!"); } public void sessionWillPassivate(HttpSessionEvent se) { System.out.println("javabean随着session到硬盘中去了!!"); } }
5、网站实现踢人效果(就是将用户的session中去除),略.