一、基本概念
JavaWeb里面的listener是通过观察者设计模式进行实现的。对于观察者模式,这里不做过多介绍,大概讲一下什么意思。
观察者模式又叫发布订阅模式或者监听器模式。在该模式中有两个角色:观察者和被观察者(通常也叫做主题)。观察者在主题里面注册自己感兴趣的事件,当这个事件发生时,主题会通过回调接口的方式通知观察者。
举个生活中的例子:订阅报纸。任何一个家庭或个人都可以向报社订阅报纸。这里报社就是“主题”,家庭就是“观察者”。比如家庭需要订阅明天早晨的报纸,这个就是“事件”。到了第二天早晨,报纸生产出来了,这个就是“事件发生”。当事件发生时,送报员将报纸送到家庭的信箱里面,这里的信箱就是“回调接口”。
对于JavaWeb里面的监听器,Servlet规范定义了一些列的Listener接口类,通过接口类的方式将事件暴露给应用程序,应用程序如果想监听其感兴趣的事件,那么不必去直接注册对应的事件,而是编写自己的listener实现相应的接口类,并将自己的listener注册到servlet容器。当程序关心的事件发生时,servlet容器会通知listener,回调listener里面的方法。这里自定义的listener就是观察者,servlet容器就是主题。
二、样例分析
上面说了,servlet容器是通过Listener接口类将事件暴露给应用程序的。所以我们与其说是注册事件,不如说是注册监听器。对应到编程步骤就是:1.编写自己的listener,实现特定的Listener接口。2.在web.xml里面注册自己的listener(也可以通过注解的方式,道理是一样的)。这里以最简单的监听器接口ServletContextListener举例:
1.TestListener.java
1 public class TestListener implements ServletContextListener { 2 3 public TestListener() {} 4 5 public void contextInitialized(ServletContextEvent sce) { 6 System.out.println("ServletContextListener.contextInitialized"); 7 } 8 9 public void contextDestroyed(ServletContextEvent sce) { 10 System.out.println("ServletContextListener.contextDestroyed"); 11 } 12 }
2.web.xml
1 <listener> 2 <listener-class>com.nantang.listener.TestListener</listener-class> 3 </listener>
当容器启动时会向日志中输出"ServletContextListener.contextInitialized",当容器关闭时会输出"ServletContextListener.contextDestroyed"。详细的解释后面会进一步分析。
这里需要注意是,如果在IDE(Eclipse、STS等)演示上面的例子,当启动服务器时,在控制台可以看到"ServletContextListener.contextInitialized",当关闭服务器时,是看不到"ServletContextListener.contextDestroyed"的。这不是没有执行contextDestroyed方法,而是IDE实现的不够完美。要想验证确实调用了contextDestroyed,可以在contextDestroyed里面写一段代码逻辑往文件输出内容而不要输出到控制台。
三、源码分析
现在我们分析下,servlet规范为我们定义了哪些事件。更准确的说是定义了哪些监听接口。下面的介绍都是以servlet3.0规范为准。
servlet3.0为我们提供了8个监听器接口,按照它们的作用域来划分的话可以分为三类:
1.servlet上下文相关监听接口,包括:ServletContextListener和ServletContextAttributeListener。
2.http session相关监听接口,包括:HttpSessionListener、HttpSessionActivationListener、HttpSessionAttributeListener和HttpSessionBindingListener。
3.servlet request相关监听接口,包括:ServletRequestListener和ServletRequestAttributeListener。
其实从接口的命名,各位看官应该能猜出其基本功能。下面我们按分类来解释。
1.servlet上下文相关监听接口
之前在介绍Servlet的时候,我们解释过一个web应用对应一个servlet上下文。所以ServletContextListener和ServletContextAttributeListener监听的事件的生命范围是贯穿整个web应用的。下面是这两个接口的类图层级关系。
1.1 EventListener
EventListener是一个标记接口,所有的事件监听器都必须继承这个接口,这就是servlet规范,没什么好解释的。
1.2 EventObject
和EventListener类似,EventObject是个事件顶级类,所有具体的事件类都必须继承EventObject。
1 public class EventObject implements java.io.Serializable { 2 3 protected transient Object source; 4 5 public EventObject(Object source) { 6 if (source == null) 7 throw new IllegalArgumentException("null source"); 8 9 this.source = source; 10 } 11 12 public Object getSource() { 13 return source; 14 } 15 16 public String toString() { 17 return getClass().getName() + "[source=" + source + "]"; 18 } 19 }
这个类很简单,其本质就一个东西:source。通过类名EventObject和属性名source,就能看出这个类就干了一件事,持有“事件源对象”。
1.3 ServletContextEvent
1 public class ServletContextEvent extends java.util.EventObject { 2 3 public ServletContextEvent(ServletContext source) { 4 super(source); 5 } 6 7 public ServletContext getServletContext () { 8 return (ServletContext) super.getSource(); 9 } 10 }
servlet上下文事件,这个事件类就是对EventObject的简单继承。构造方法中提供ServletContext实例作为事件源。因为事件源是servlet上下文,所以提供个getServletContext获取ServletContext实例。
在我们后续讲解其他事件类的时候,都是一个模子,每个事件类都提供相应的构造方法,传入相应的事件源对象,并提供额外的获取事件源方法。所以EventObject就是个事件源的基类,所有事件子类的本质就干了一件事,确定具体的事件源对象。
所以我们后面讲解事件的地方,一带而过。
1.4 ServletContextListener
1 public interface ServletContextListener extends EventListener { 2 3 public void contextInitialized ( ServletContextEvent sce ); 4 5 public void contextDestroyed ( ServletContextEvent sce ); 6 }
servlet上下文监听器接口,对应着两个事件:servlet上下文初始化事件和servlet上下文即将关闭事件。
当web应用初始化的时候,servlet容器会构造ServletContextEven实例,并回调contextInitialize方法。
当servlet上下文即将关闭时,一般是关闭服务器之前,servlet容器会构造ServletContextEven实例,并回调contextDestroyed方法。这里需要注意的是,contextDestroyed方法的执行会在所有的servlet和filter执行完destroy方法之后。
所以如果我们想在应用启动或关闭时需要做些事情的话,就编写自己的listener实现该接口。
所有的事件监听器也是一个模子,按照servlet规范定义相应的事件回调接口方法,方法的入参就是相应的事件源实例。所以我们后面讲解监听器的地方也一带而过。
1.5 ServletContextAttributeEvent
1 public class ServletContextAttributeEvent extends ServletContextEvent { 2 private String name; 3 private Object value; 4 5 public ServletContextAttributeEvent(ServletContext source, String name, Object value) { 6 super(source); 7 this.name = name; 8 this.value = value; 9 } 10 11 public String getName() { 12 return this.name; 13 } 14 15 public Object getValue() { 16 return this.value; 17 } 18 }
ServletContextAttributeEvent表示servlet上下文属性相关事件,一般当属性发生改变时会触发该事件。这个类继承ServletContextEven,事件源也是ServletContext实例。额外提供属性名和属性值的获取方法。
1.6 ServletContextAttributeListener
1 public interface ServletContextAttributeListener extends EventListener { 2 3 public void attributeAdded(ServletContextAttributeEvent scab); 4 5 public void attributeRemoved(ServletContextAttributeEvent scab); 6 7 public void attributeReplaced(ServletContextAttributeEvent scab); 8 }
当servlet上文属性发生增、删、改的时候,servlet容器构造ServletContextAttributeEvent事件对象,分别回调attributeAdded、attributeRemoved、attributeReplaced方法。
这里需要注意的是attributeReplaced方法,当属性的值被替换的时候回调。这个时候如果调用ServletContextAttributeEvent.getValue()方法返回的是替换之前的属性值。
2 http session相关监听接口
2.1 HttpSessionEvent
1 public class HttpSessionEvent extends java.util.EventObject {
2
3 public HttpSessionEvent(HttpSession source) {
4 super(source);
5 }
6
7 public HttpSession getSession () {
8 return (HttpSession) super.getSource();
9 }
10 }
http session相关事件,当session发生变化时会触发该事件。事件源是HttpSession实例,并提供额外的HttpSession获取方法。
2.2 HttpSessionListener
1 public interface HttpSessionListener extends EventListener { 2 3 public void sessionCreated ( HttpSessionEvent se ); 4 5 public void sessionDestroyed ( HttpSessionEvent se ); 6 }
当session被创建和销毁的时候,servlet容器构造HttpSessionEvent事件对象,并回调sessionCreated和sessionDestroyed方法。
2.3 HttpSessionActivationListener
1 public interface HttpSessionActivationListener extends EventListener { 2 3 public void sessionWillPassivate(HttpSessionEvent se); 4 5 public void sessionDidActivate(HttpSessionEvent se); 6 }
当session将要钝化或已被激活时,servlet容器构造HttpSessionEvent事件对象,回调sessionWillPassivate和sessionDidActivate方法。
这里解释下钝化和激活:钝化是指服务器内存不够了或者session的活动超时时间到了,把最近不活动的session序列化到磁盘。激活是指某个钝化的session又被访问了,从磁盘将session反序列化到内存。
这里可以看出要想钝化和激活,首先session得可序列化和反序列化。同时我们在编程过程中,session尽量用String、Integer等简单的对象,尽量不要用list、map等集合。
2.4 HttpSessionBindingEvent
1 public class HttpSessionBindingEvent extends HttpSessionEvent { 2 3 private String name; 4 private Object value; 5 6 public HttpSessionBindingEvent(HttpSession session, String name) { 7 super(session); 8 this.name = name; 9 } 10 11 public HttpSessionBindingEvent(HttpSession session, String name, Object value) { 12 super(session); 13 this.name = name; 14 this.value = value; 15 } 16 17 public HttpSession getSession () { 18 return super.getSession(); 19 } 20 21 public String getName() { 22 return name; 23 } 24 25 public Object getValue() { 26 return this.value; 27 } 28 }
http session的属性相关事件,当session属性发生变化时会触发该事件。事件源是HttpSession实例,并提供额外的获取HttpSession、属性名、属性值的方法。
2.5 HttpSessionAttributeListener
1 public interface HttpSessionAttributeListener extends EventListener { 2 3 public void attributeAdded ( HttpSessionBindingEvent se ); 4 5 public void attributeRemoved ( HttpSessionBindingEvent se ); 6 7 public void attributeReplaced ( HttpSessionBindingEvent se ); 8 }
当session属性发生增、删、改的时候,servlet容器构造HttpSessionBindingEvent事件对象,分别回调attributeAdded、attributeRemoved、attributeReplaced方法。
这里需要注意的是attributeReplaced方法,当属性的值被替换的时候回调。这个时候如果调用ServletContextAttributeEvent.getValue()方法返回的是替换之前的属性值。
当调用session的invalidate方法或者session失效时,也会回调attributeRemoved方法。
2.6 HttpSessionBindingListener
1 public interface HttpSessionBindingListener extends EventListener { 2 3 public void valueBound(HttpSessionBindingEvent event); 4 5 public void valueUnbound(HttpSessionBindingEvent event); 6 }
这个监听器也是监听session的属性变化。当session属性发生增和删,也就是属性值绑定和属性值解绑的时候,servlet容器构造HttpSessionBindingEvent事件对象,分别回调valueBound、valueUnbound方法。
这么一看和HttpSessionAttributeListener没什么区别,其实不是这样。两者有个本质的区别就是事件触发的条件。
当session的属性有任何的变化,servlet容器都会通知HttpSessionAttributeListener。但是对于HttpSessionBindingListener,只有当绑定或解绑的属性值是监听器的实例时,servlet容器才会通知。举例来说:
1 public class TestListener implements HttpSessionBindingListener{ 2 @Override 3 public void valueBound(HttpSessionBindingEvent event) { 4 System.out.println("HttpSessionBindingListener.valueBound"); 5 } 6 @Override 7 public void valueUnbound(HttpSessionBindingEvent event) { 8 System.out.println("HttpSessionBindingListener.valueUnbound"); 9 } 10 }
我们自定义监听器TestListener实现HttpSessionBindingListener,下面我们在代码中设置如下session属性:
1 HttpSession session = request.getSession(); 2 TestListener testListener=new TestListener(); 3 session.setAttribute("listener", testListener); 4 session.removeAttribute("listener");
这里session的属性值是我们的监听器TestListener实例。所以这段代码执行时,servlet容器会通知TestListener并回调valueBound和valueUnbound方法。
这里需要注意的是,当调用session的invalidate方法或者session失效时,也会回调valueUnbound方法。
3 servlet request相关监听接口
3.1 ServletRequestEvent
1 public class ServletRequestEvent extends java.util.EventObject { 2 private ServletRequest request; 3 4 public ServletRequestEvent(ServletContext sc, ServletRequest request) { 5 super(sc); 6 this.request = request; 7 } 8 9 public ServletRequest getServletRequest () { 10 return this.request; 11 } 12 13 public ServletContext getServletContext () { 14 return (ServletContext) super.getSource(); 15 } 16 }
servlet请求的相关事件,当request发生变化时会触发该事件。事件源是ServletContext实例,并提供额外的获取ServletContext和ServletRequest方法。
3.2 ServletRequestListener
1 public interface ServletRequestListener extends EventListener { 2 3 public void requestDestroyed ( ServletRequestEvent sre ); 4 5 public void requestInitialized ( ServletRequestEvent sre ); 6 }
当请求初始化或者销毁时,即客户端请求进入web应用(进入servlet或者第一个filter)或web应用返回响应给客户端(退出servlet或者第一个filter)。servlet容器构造ServletRequestEvent实例,回调requestInitialized和requestDestroyed方法。
3.3 ServletRequestAttributeEvent
1 public class ServletRequestAttributeEvent extends ServletRequestEvent { 2 private String name; 3 private Object value; 4 5 public ServletRequestAttributeEvent(ServletContext sc, ServletRequest request, String name, Object value) { 6 super(sc, request); 7 this.name = name; 8 this.value = value; 9 } 10 11 public String getName() { 12 return this.name; 13 } 14 15 public Object getValue() { 16 return this.value; 17 } 18 }
servlet请求属性的相关事件,当请求属性发生变化时会触发该事件。事件源是ServletContext实例,并提供额外的获取属性名和属性值的方法。
3.4 ServletRequestAttributeListener
1 public interface ServletRequestAttributeListener extends EventListener { 2 3 public void attributeAdded(ServletRequestAttributeEvent srae); 4 5 public void attributeRemoved(ServletRequestAttributeEvent srae); 6 7 public void attributeReplaced(ServletRequestAttributeEvent srae); 8 }
当请求的属性发生增、删、改的时候,servlet容器构造ServletRequestAttributeEvent事件对象,分别回调attributeAdded、attributeRemoved、attributeReplaced方法。
这里需要注意的是attributeReplaced方法,当属性的值被替换的时候回调。这个时候如果调用ServletRequestAttributeEvent.getValue()方法返回的是替换之前的属性值。
四、总结
至此,listener讲完了。我们可以发现listener和servlet、filter有个共同点,都是由容器进行调度。我们只需要编写自己的listener去实现我们关心的监听器接口并注册,剩下来的工作就是在我们自己的listener里面编写业务逻辑。
这边博文介绍的listener是servlet3.0规范制定的。3.1已经增加了一些事件监听器接口,道理都是类似的,读者可以自行去了解。
作者:南唐三少
出处:http://www.cnblogs.com/nantang
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我们最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文链接,否则保留追究法律责任的权利。