JavaWeb 监听器

来源:互联网 发布:淘宝店铺签到送淘金币 编辑:程序博客网 时间:2024/05/17 09:31

一、事件监听机制

       事件监听机制涉及到三个组件:事件源、事件监听器、事件对象。当事件源上发生操作时,事件源会调用事件处理器的一个方法响应操作,并且在调用方法时还会把事件对象传递给事件处理器。事件处理器由程序员编写,程序员通过事件对象可以知道哪个事件源上发生了操作,从而可以对操作进行处理。

 

二、Servlet 监听器

       在 Servlet 规范中定义了多种类型的监听器,它们用于监听的事件源分别为:ServletContext、HttpSession、ServletRequest 这三个域对象。Servlet 规范针对这三个对象上的操作,又把这多种类型的监听器划分为三种类型:

         1、监听三个域对象创建和销毁的事件监听器

         2、监听域对象中属性的增加和删除的事件监听器

         3、监听绑定到 HttpSession 域中的某个对象的状态的事件监听器。

 

三、监听 ServletContext 域对象创建和销毁

       ServletContextListener 接口用于监听 ServletContext 对象的创建和销毁事件。当 ServletContext 对象被创建时会激发 contextInitialized 方法。当 ServletContext 对象被销毁时,会激发 contextDestroyed 方法。

       ServletContextListener 监听器用于对 Web 应用的初始化和终结操作,例如当 Web 应用启动时加载一个数据库连接池,当 Web 应用关闭时释放数据库连接池。

[java] view plaincopyprint?
  1. package cn.dk.listener.context;  
  2.   
  3. import javax.servlet.ServletContextEvent;  
  4. import javax.servlet.ServletContextListener;  
  5.   
  6. public class ContextListener implements ServletContextListener {  
  7.   
  8.     public void contextDestroyed(ServletContextEvent sce) {  
  9.         System.out.println("web应用结束");  
  10.     }  
  11.   
  12.     public void contextInitialized(ServletContextEvent sce) {  
  13.         System.out.println("web应用开始");  
  14.     }  
  15. }  


四、监听 HttpSession 域对象创建和销毁

       HttpSessionListener 接口用于监听 HttpSession 的创建和销毁。创建一个 Session 时,sssionCreated 方法会被调用。销毁一个 Session 时,sessionDestroyed 方法会被调用。Session 域对象创建和销毁:用户每一次访问时,服务器会创建一个 session(在 getSession()时),销毁:用户的 session 30分钟没有被使用,服务器会销毁 session。如果 Jsp 页面中设置了 session=false,那么只有当 request.getSession() 时,Session 对象才会被创建。

[java] view plaincopyprint?
  1. package cn.dk.listener.context;  
  2.   
  3. import javax.servlet.http.HttpSessionEvent;  
  4. import javax.servlet.http.HttpSessionListener;  
  5.   
  6. public class SessionListener implements HttpSessionListener {  
  7.   
  8.     public void sessionCreated(HttpSessionEvent se) {  
  9.         System.out.println("创建session");  
  10.     }  
  11.   
  12.     public void sessionDestroyed(HttpSessionEvent se) {  
  13.         System.out.println("销毁session");  
  14.     }  
  15. }  


五、监听 HttpRequest 域对象创建和销毁

       ServletRequestListener 接口用于监听 ServletRequest 对象的创建和销毁。Request 对象被创建时,监听器的 requestInitialized 方法会被调用。Request 对象被销毁时,监听器的 requestDestroyed 方法会被调用。ServletRequest 域对象创建和销毁的时机:用户每一次访问都是一次新的请求,都会创建爱你一个新的 request。当访问结束,也就是请求结束时 request 会立即销毁。所以刷新一次,监听器的创建和销毁事件会被激发一次。

[java] view plaincopyprint?
  1. package cn.dk.listener.context;  
  2.   
  3. import javax.servlet.ServletRequestEvent;  
  4. import javax.servlet.ServletRequestListener;  
  5.   
  6. public class RequestListener implements ServletRequestListener {  
  7.   
  8.     public void requestDestroyed(ServletRequestEvent sre) {  
  9.         System.out.println("request销毁了");  
  10.     }  
  11.   
  12.     public void requestInitialized(ServletRequestEvent sre) {  
  13.         System.out.println("request创建了");  
  14.     }  
  15. }  

 

六、利用 HttpSessionListener 编写粗略在线人数统计

[java] view plaincopyprint?
  1. package cn.dk.count;  
  2.   
  3. import javax.servlet.ServletContext;  
  4. import javax.servlet.http.HttpSessionEvent;  
  5. import javax.servlet.http.HttpSessionListener;  
  6.   
  7. public class CountListener implements HttpSessionListener {  
  8.   
  9.     public void sessionCreated(HttpSessionEvent se) {  
  10.         ServletContext servletContext = se.getSession().getServletContext();  
  11.         Integer count = (Integer) servletContext.getAttribute("count");  
  12.         if(count == null)  
  13.             count = 1;  
  14.         else  
  15.             count ++;  
  16.         servletContext.setAttribute("count", count);  
  17.     }  
  18.   
  19.     public void sessionDestroyed(HttpSessionEvent se) {  
  20.         ServletContext servletContext = se.getSession().getServletContext();  
  21.         Integer count = (Integer) servletContext.getAttribute("count");  
  22.         servletContext.setAttribute("count", --count);  
  23.     }  
  24. }  


七、自定义 Session 扫描器

       难点分析:我们为了精确的统计出当前在线人数,就必须及时将不活动的 Session 对象销毁掉。所以我们就必须定时的扫描当前 ServletContext 中所有的 Session。当服务器为一个会话创建一个 Session 时,我们需要将这个 Session 对象存储到集合中,利用定时器来定时遍历这个集合中的所有 Session 得到 Session 的 lastAccessedTime 从而进行判断。原理很简单,但是我们必须考虑到线程安全的问题。

         首先,如果有多个用户并发访问服务器,那么服务器极有可能同时执行 list.add(session) 的方法,那么就可能造成后进来的 session 将前面的 session 覆盖掉,导致丢失了其他 session 的管理。这就是集合并发访问的问题,为了解决线程安全问题,我们可以将这个 add 方法放到 synchronized 代码块中,也可以在创建集合的时候指定一个线程安全的集合。Collections.synchronizedList(new LinkedList<HttpSession>()); 这样就保证了并发访问安全。

         其次,当我们在遍历集合,发现有不活动的 Session 时,需要将 Session 摧毁掉,并从集合中移除。这时就会引发集合并发访问异常。(因此我们在迭代集合的时候不要轻易的调用集合的方法。)我们可以通过迭代器的 remove() 方法来移除集合中的元素。另外还可以使用 ListIterator 迭代器,ListIterator 迭代器可以在迭代的时候对元素进行增删改查。

         最后,当我们在遍历集合的时候,刚好有一个用户进来,list.add(session) 这时候又会引发集合并发访问的异常。我们可以调用迭代器的增加方法,还可以将增加方法和迭代方法放在一个同步代码块中,两个同步代码块有着相同的锁旗标对象。这样也可以解决问题。

         总结:1、并发向集合中增加元素,我们可以使用同步代码块也可以使用 Collections.synchronized...构建线程安全的集合。

                   2、在对集合进行迭代的时候,谨慎调用集合的操作。如果有对集合的操作我们应该用迭代器来操作集合。

                   3、对同一集合在不同地方操作时,也要注意集合并发访问的问题,这时我们需要将这些方法同步,以保证线程安全。

[java] view plaincopyprint?
  1. package cn.dk.sessionScanner;  
  2.   
  3. import java.util.Collections;  
  4. import java.util.Iterator;  
  5. import java.util.LinkedList;  
  6. import java.util.List;  
  7. import java.util.Timer;  
  8. import java.util.TimerTask;  
  9. import javax.servlet.ServletContextEvent;  
  10. import javax.servlet.ServletContextListener;  
  11. import javax.servlet.http.HttpSession;  
  12. import javax.servlet.http.HttpSessionEvent;  
  13. import javax.servlet.http.HttpSessionListener;  
  14.   
  15. public class SessionScanner implements HttpSessionListener,  
  16.         ServletContextListener {  
  17.   
  18.     private static final List<HttpSession> SESSIONS = Collections.synchronizedList(new LinkedList<HttpSession>());  
  19.     private final static Object LOCK = new Object();;  
  20.   
  21.     public void sessionCreated(HttpSessionEvent se) {  
  22.         System.out.println("Session创建了");  
  23.         synchronized (LOCK) {  
  24.             SESSIONS.add(se.getSession());  
  25.         }  
  26.     }  
  27.   
  28.     public void sessionDestroyed(HttpSessionEvent se) {  
  29.         System.out.println("Session摧毁了");  
  30.     }  
  31.   
  32.     public void contextInitialized(ServletContextEvent sce) {  
  33.         Timer timer = new Timer();  
  34.         timer.schedule(new TimerTask() {  
  35.             public void run() {  
  36.                 synchronized (LOCK) {  
  37.                     Iterator<HttpSession> iterator = SESSIONS.iterator();  
  38.                     while (iterator.hasNext()) {  
  39.                         HttpSession session = iterator.next();  
  40.                         System.out.println(session.getLastAccessedTime());  
  41.                         System.out.println(System.currentTimeMillis());  
  42.                         if (System.currentTimeMillis()  
  43.                                 - session.getLastAccessedTime() > 1000 * 60) {  
  44.                             session.invalidate();  
  45.                             iterator.remove();  
  46.                         }  
  47.                     }  
  48.                 }  
  49.             }  
  50.         }, 01000 * 10);  
  51.     }  
  52.   
  53.     public void contextDestroyed(ServletContextEvent sce) {  
  54.     }  
  55. }  


八、观察者设计模式

       上面我们一直是在写监听器来监听别人写的对象。我们自己写的对象也可以被别人监听。这就是观察者设计模式,就是说我们写的对象可以被别人观察和监听,要想写出被别人监听的对象,我们需要事件源、监听器、事件对象。在事件源中维护这一个事件监听器对象,当我们调用事件源的某个方法时,会调用事件监听器中约定的方法,事件监听器是一个接口,约定了事件源中所有的行为,事件对象用来描述事件源,封装事件源,以便监听器中可以获取事件源相关信息。在事件源中的每个方法中,将事件对象中的事件源指向自己。

[java] view plaincopyprint?
  1. package cn.dk.observer;  
  2.   
  3. public class Person {  
  4.   
  5.     private PersonListener listener;  
  6.   
  7.     public Person(PersonListener listener) {  
  8.         super();  
  9.         this.listener = listener;  
  10.     }  
  11.   
  12.     public void eat() {  
  13.         Event source = new Event(this);  
  14.         listener.eat(source);  
  15.     }  
  16.   
  17.     public void run() {  
  18.         Event source = new Event(this);  
  19.         listener.run(source);  
  20.     }  
  21. }  
  22.   
  23. interface PersonListener {  
  24.     public void eat(Event source);  
  25.   
  26.     public void run(Event source);  
  27. }  
  28.   
  29. class Event {  
  30.     private Person source;  
  31.   
  32.     public Person getSource() {  
  33.         return this.source;  
  34.     }  
  35.   
  36.     public Event(Person source) {  
  37.         super();  
  38.         this.source = source;  
  39.     }  
  40. }  
[java] view plaincopyprint?
  1. package cn.dk.observer;  
  2.   
  3. public class PersonObserver {  
  4.   
  5.     public static void main(String[] args) {  
  6.         Person person = new Person(new PersonListener(){  
  7.   
  8.             public void eat(Event source) {  
  9.                 System.out.println(source.getSource() + "吃了");  
  10.                   
  11.             }  
  12.   
  13.             public void run(Event source) {  
  14.                 System.out.println(source.getSource() + "跑了");  
  15.                   
  16.             }});  
  17.           
  18.         person.eat();  
  19.         person.run();  
  20.     }  
  21. }  


九、监听三个域对象属性变化

       ServletContextAttributeListener

         HttpSessionAttributeListener

         ServletRequestAttributeListener

         这三个接口中定义了三个方法来吃力被监听对象中的属性增加,删除和替换的时间,同一个时间在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。

 

十、感知 Session 绑定的事件监听器

       保存在 Session 域中的对象可以有多种状态:绑定到 Session 中,从 Session 中解除绑定,随 Session 对象持久化到存储设备中(钝化),随 Session 对象从一个存储设备中恢复(活化)。Servlet 规范中定义了两个特殊的监听器接口来感知以上两种状态。

       1、HttpSessionBindingListener 某个 Bean 对象实现了这个接口,就可以感知自己被绑定到 Session 中或自己从 Session 中解除绑定。

[java] view plaincopyprint?
  1. package cn.dk.binding;  
  2.   
  3. import javax.servlet.http.HttpSessionBindingEvent;  
  4. import javax.servlet.http.HttpSessionBindingListener;  
  5.   
  6. public class Person implements HttpSessionBindingListener{  
  7.   
  8.     public void valueBound(HttpSessionBindingEvent event) {  
  9.         System.out.println("绑定到session");  
  10.     }  
  11.   
  12.     public void valueUnbound(HttpSessionBindingEvent event) {  
  13.         System.out.println("解除绑定");  
  14.     }  
  15. }  


         2、HttpSessionActivationListener 某个 Bean 对象实现了这个接口,就可以感知自己是否随 Session 一起序列化到硬盘,或者从硬盘恢复到内存中去。服务器默认为一个 Session 保存半小时,这是非常耗资源的,所以我们可以采取将短时间不用的 Session 序列化到硬盘,以减轻服务器内存压力,当再使用的时候再从硬盘恢复到内存中去。

[java] view plaincopyprint?
  1. package cn.dk.binding;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. import javax.servlet.http.HttpSessionActivationListener;  
  6. import javax.servlet.http.HttpSessionBindingEvent;  
  7. import javax.servlet.http.HttpSessionBindingListener;  
  8. import javax.servlet.http.HttpSessionEvent;  
  9.   
  10. public class Person implements HttpSessionBindingListener,HttpSessionActivationListener,Serializable{  
  11.   
  12.     public void valueBound(HttpSessionBindingEvent event) {  
  13.         System.out.println("绑定到session");  
  14.     }  
  15.   
  16.     public void valueUnbound(HttpSessionBindingEvent event) {  
  17.         System.out.println("解除绑定");  
  18.     }  
  19.   
  20.     public void sessionDidActivate(HttpSessionEvent se) {  
  21.         System.out.println("活化了");  
  22.     }  
  23.   
  24.     public void sessionWillPassivate(HttpSessionEvent se) {  
  25.         System.out.println("钝化了");  
  26.     }  
  27. }  


九、HttpSessionAttributeListener 监听器实现网站踢人功能

       思路分析:每个用户登录后,会将用户信息存放在 session 中,当向 session 中增加属性时,会被 HttpSessionAttributeListener 监听器捕获到。首先判断添加的属性是否为 User 类型,如果是,则从 ServletContext 拿出 key 为用户名 value 为相对应的用户 session 的 Map 集合,将此用户登录信息保存于 Map 中。管理员点击踢人超链接,将用户名以参数带给 Servlet,在 Servlet 中取出 ServletContext 中的 Map 获取到被踢用户的 Session,将用户登录信息从 Session 属性中移除。当移除 Session 属性时,又会被 HttpSessionAttributeListener 监听器捕获到,在 HttpSessionAttributeListener 监听器内部,获取到被踢用户对象,再从 ServletContext 中的 Map 集合中移除掉。

 

[java] view plaincopyprint?
  1. package cn.dk.kick;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5. import javax.servlet.ServletContext;  
  6. import javax.servlet.http.HttpSession;  
  7. import javax.servlet.http.HttpSessionAttributeListener;  
  8. import javax.servlet.http.HttpSessionBindingEvent;  
  9.   
  10. public class SessionListener implements HttpSessionAttributeListener {  
  11.   
  12.     @SuppressWarnings("unchecked")  
  13.     public void attributeAdded(HttpSessionBindingEvent se) {  
  14.         ServletContext servletContext = se.getSession().getServletContext();  
  15.         Map<String, HttpSession> map = (Map<String, HttpSession>) servletContext.getAttribute("map");  
  16.         Object value = se.getValue();  
  17.         if(value instanceof User){  
  18.             if(map == null)  
  19.                 map = new HashMap<String, HttpSession>();  
  20.             map.put(((User)value).getUsername(), se.getSession());  
  21.         }  
  22.         servletContext.setAttribute("map", map);  
  23.     }  
  24.   
  25.     @SuppressWarnings("unchecked")  
  26.     public void attributeRemoved(HttpSessionBindingEvent se) {  
  27.         System.out.println("remove");  
  28.         ServletContext servletContext = se.getSession().getServletContext();  
  29.         Map<String, HttpSession> map = (Map<String, HttpSession>) servletContext.getAttribute("map");  
  30.         map.remove(((User)se.getValue()).getUsername());  
  31.     }  
  32.   
  33.     public void attributeReplaced(HttpSessionBindingEvent se) {  
  34.     }  
  35. }  


 

[java] view plaincopyprint?
  1. package cn.dk.kick;  
  2.   
  3. import java.io.IOException;  
  4. import javax.servlet.ServletException;  
  5. import javax.servlet.http.HttpServlet;  
  6. import javax.servlet.http.HttpServletRequest;  
  7. import javax.servlet.http.HttpServletResponse;  
  8.   
  9. public class LoginServlet extends HttpServlet {  
  10.   
  11.     @SuppressWarnings("unchecked")  
  12.     public void doGet(HttpServletRequest request, HttpServletResponse response)  
  13.             throws ServletException, IOException {  
  14.         Service service = new Service();  
  15.         String username = request.getParameter("username");  
  16.         String password = request.getParameter("password");  
  17.         User user = service.login(username, password);  
  18.         request.getSession().setAttribute("user", user);  
  19.         response.sendRedirect(request.getContextPath() + "/index.jsp");  
  20.     }  
  21.   
  22.     public void doPost(HttpServletRequest request, HttpServletResponse response)  
  23.             throws ServletException, IOException {  
  24.         doGet(request, response);  
  25.     }  
  26. }  


 

[java] view plaincopyprint?
  1. package cn.dk.kick;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.Map;  
  5. import javax.servlet.ServletException;  
  6. import javax.servlet.http.HttpServlet;  
  7. import javax.servlet.http.HttpServletRequest;  
  8. import javax.servlet.http.HttpServletResponse;  
  9. import javax.servlet.http.HttpSession;  
  10.   
  11. public class KickServlet extends HttpServlet {  
  12.   
  13.     @SuppressWarnings("unchecked")  
  14.     public void doGet(HttpServletRequest request, HttpServletResponse response)  
  15.             throws ServletException, IOException {  
  16.         Map<String, HttpSession> map = (Map<String, HttpSession>) getServletContext().getAttribute("map");  
  17.         HttpSession session = map.get(request.getParameter("username"));  
  18.         session.removeAttribute("user");  
  19.         response.sendRedirect(request.getContextPath() + "/index.jsp");  
  20.     }  
  21.   
  22.     public void doPost(HttpServletRequest request, HttpServletResponse response)  
  23.             throws ServletException, IOException {  
  24.         doGet(request, response);  
  25.     }  
  26. }  
原创粉丝点击