JavaWeb 监听器

来源:互联网 发布:目前心脑血管疾病数据 编辑:程序博客网 时间:2024/06/06 17:59

一、事件监听机制

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

 

二、Servlet 监听器

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

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

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

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

 

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

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

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

package cn.dk.listener.context;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;public class ContextListener implements ServletContextListener {public void contextDestroyed(ServletContextEvent sce) {System.out.println("web应用结束");}public void contextInitialized(ServletContextEvent sce) {System.out.println("web应用开始");}}


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

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

package cn.dk.listener.context;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;public class SessionListener implements HttpSessionListener {public void sessionCreated(HttpSessionEvent se) {System.out.println("创建session");}public void sessionDestroyed(HttpSessionEvent se) {System.out.println("销毁session");}}


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

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

package cn.dk.listener.context;import javax.servlet.ServletRequestEvent;import javax.servlet.ServletRequestListener;public class RequestListener implements ServletRequestListener {public void requestDestroyed(ServletRequestEvent sre) {System.out.println("request销毁了");}public void requestInitialized(ServletRequestEvent sre) {System.out.println("request创建了");}}

 

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

package cn.dk.count;import javax.servlet.ServletContext;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;public class CountListener implements HttpSessionListener {public void sessionCreated(HttpSessionEvent se) {ServletContext servletContext = se.getSession().getServletContext();Integer count = (Integer) servletContext.getAttribute("count");if(count == null)count = 1;elsecount ++;servletContext.setAttribute("count", count);}public void sessionDestroyed(HttpSessionEvent se) {ServletContext servletContext = se.getSession().getServletContext();Integer count = (Integer) servletContext.getAttribute("count");servletContext.setAttribute("count", --count);}}


七、自定义 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、对同一集合在不同地方操作时,也要注意集合并发访问的问题,这时我们需要将这些方法同步,以保证线程安全。

package cn.dk.sessionScanner;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 {private static final List<HttpSession> SESSIONS = Collections.synchronizedList(new LinkedList<HttpSession>());private final static Object LOCK = new Object();;public void sessionCreated(HttpSessionEvent se) {System.out.println("Session创建了");synchronized (LOCK) {SESSIONS.add(se.getSession());}}public void sessionDestroyed(HttpSessionEvent se) {System.out.println("Session摧毁了");}public void contextInitialized(ServletContextEvent sce) {Timer timer = new Timer();timer.schedule(new TimerTask() {public void run() {synchronized (LOCK) {Iterator<HttpSession> iterator = SESSIONS.iterator();while (iterator.hasNext()) {HttpSession session = iterator.next();System.out.println(session.getLastAccessedTime());System.out.println(System.currentTimeMillis());if (System.currentTimeMillis()- session.getLastAccessedTime() > 1000 * 60) {session.invalidate();iterator.remove();}}}}}, 0, 1000 * 10);}public void contextDestroyed(ServletContextEvent sce) {}}


八、观察者设计模式

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

package cn.dk.observer;public class Person {private PersonListener listener;public Person(PersonListener listener) {super();this.listener = listener;}public void eat() {Event source = new Event(this);listener.eat(source);}public void run() {Event source = new Event(this);listener.run(source);}}interface PersonListener {public void eat(Event source);public void run(Event source);}class Event {private Person source;public Person getSource() {return this.source;}public Event(Person source) {super();this.source = source;}}
package cn.dk.observer;public class PersonObserver {public static void main(String[] args) {Person person = new Person(new PersonListener(){public void eat(Event source) {System.out.println(source.getSource() + "吃了");}public void run(Event source) {System.out.println(source.getSource() + "跑了");}});person.eat();person.run();}}


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

       ServletContextAttributeListener

         HttpSessionAttributeListener

         ServletRequestAttributeListener

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

 

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

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

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

package cn.dk.binding;import javax.servlet.http.HttpSessionBindingEvent;import javax.servlet.http.HttpSessionBindingListener;public class Person implements HttpSessionBindingListener{public void valueBound(HttpSessionBindingEvent event) {System.out.println("绑定到session");}public void valueUnbound(HttpSessionBindingEvent event) {System.out.println("解除绑定");}}


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

package cn.dk.binding;import java.io.Serializable;import javax.servlet.http.HttpSessionActivationListener;import javax.servlet.http.HttpSessionBindingEvent;import javax.servlet.http.HttpSessionBindingListener;import javax.servlet.http.HttpSessionEvent;public class Person implements HttpSessionBindingListener,HttpSessionActivationListener,Serializable{public void valueBound(HttpSessionBindingEvent event) {System.out.println("绑定到session");}public void valueUnbound(HttpSessionBindingEvent event) {System.out.println("解除绑定");}public void sessionDidActivate(HttpSessionEvent se) {System.out.println("活化了");}public void sessionWillPassivate(HttpSessionEvent se) {System.out.println("钝化了");}}


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

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

 

package cn.dk.kick;import java.util.HashMap;import java.util.Map;import javax.servlet.ServletContext;import javax.servlet.http.HttpSession;import javax.servlet.http.HttpSessionAttributeListener;import javax.servlet.http.HttpSessionBindingEvent;public class SessionListener implements HttpSessionAttributeListener {@SuppressWarnings("unchecked")public void attributeAdded(HttpSessionBindingEvent se) {ServletContext servletContext = se.getSession().getServletContext();Map<String, HttpSession> map = (Map<String, HttpSession>) servletContext.getAttribute("map");Object value = se.getValue();if(value instanceof User){if(map == null)map = new HashMap<String, HttpSession>();map.put(((User)value).getUsername(), se.getSession());}servletContext.setAttribute("map", map);}@SuppressWarnings("unchecked")public void attributeRemoved(HttpSessionBindingEvent se) {System.out.println("remove");ServletContext servletContext = se.getSession().getServletContext();Map<String, HttpSession> map = (Map<String, HttpSession>) servletContext.getAttribute("map");map.remove(((User)se.getValue()).getUsername());}public void attributeReplaced(HttpSessionBindingEvent se) {}}


 

package cn.dk.kick;import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class LoginServlet extends HttpServlet {@SuppressWarnings("unchecked")public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {Service service = new Service();String username = request.getParameter("username");String password = request.getParameter("password");User user = service.login(username, password);request.getSession().setAttribute("user", user);response.sendRedirect(request.getContextPath() + "/index.jsp");}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}


 

package cn.dk.kick;import java.io.IOException;import java.util.Map;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;public class KickServlet extends HttpServlet {@SuppressWarnings("unchecked")public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {Map<String, HttpSession> map = (Map<String, HttpSession>) getServletContext().getAttribute("map");HttpSession session = map.get(request.getParameter("username"));session.removeAttribute("user");response.sendRedirect(request.getContextPath() + "/index.jsp");}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}



 

原创粉丝点击