Listener技术在Servlet环境下的应用 —— 小key出品
在spring横行的今天,Servlet的一些基本技术可能不被人们重视。其实在Servlet中还是有一些很有意思的技术细节的,比如listener就是
其中之一。
本文的目的是讨论一下Servlet环境下如何配置和使用相关的listener
事件与java.util.EventObject
事件,Event,是一个GUI编程的概念。所谓事件驱动,是指Windows编程时,由一些相应的事件,如鼠标操作、按钮点击等,触发的操作。
在Java编程理念中,事件是被封装成一个事件对象。这种理念从AWT界面编程开始
就已经实现在Java语言中。
于是,从那时起,就有一个作为各种事件的公共父类:EventObject
在Web编程时代,事件依然起源于EventObject。这个对象很简单,但很实用,
它有一个带参数的构造函数,以及两个方法:# source : Object
+ EventObject(Object source)
+ getSource() : Object
+ toString() : StringSource是事件源,就是由谁来触发这个事件。整个EventObject可以简单
描述成:谁做了什么 —— 这正好就是事件的意义
最简单的事件:生命周期事件对象
页面、请求、Session和Context构成了Servlet的四个处理层次。(一般只针对JSP说页面)。
Context,即ServletContext,在同一个JVM环境中只有一个。
所以,Context是一个全局性的处理层次。
Session,如果没有显式指定<%@page session="false" %>,
每个用户连接到我们的Web App后,都有机会建立他们自己专用,
而同时在多个页面进行共享的session。这是一个紧次于Context的,
以用户为单位的全局层次。
Request则代表了同一次用户请求的处理过程。这是一个粒度比较
小的层次。
无论是Request还是Session还是Context,都有着自身的生命周期。
不同阶段的转换,会导致事件的产生。为了表证这些事件,我们建立
了事件对象:
ServletRequestEvent extends EventObject
HttpSessionEvent extends EventObject
ServletContextEvent extends EventObject
走近生命周期事件对象
从java.util.EventObject的构成,我们可以很容量想到:这三个事件对象的source是谁?ServletRequestEvent -> ServletRequest -> + getServletRequest() : ServletRequest ***
HttpSessionEvent -> HttpSession -> getSession() : HttpSession
ServletContextEvent -> ServletContext -> getServletContext() : ServletContext其中 *** 处,即ServletRequestEvent是有错的,
事实上系统把ServletContext也作为了这个事件的源,下面是相关的源代码: 38 public ServletRequestEvent(ServletContext sc, ServletRequest request) {
39 super(sc);
40 this.request = request;
41 }
42
43 /**
44 * Returns the ServletRequest that is changing.
45 */
46 public ServletRequest getServletRequest () {
47 return this.request;
48 }
49
50 /**
51 * Returns the ServletContext of this web application.
52 */
53 public ServletContext getServletContext () {
54 return (ServletContext) super.getSource();
55 }
属性变化而触发的事件
除了生命周期外,另一个很需要重视的事件是不同级别的属性触发的事件。我们知道,Servlet有三个数据共享级别(这里不算页面一级),分别为:
Request
Session
Context
对应的类是:
ServletRequest/HttpServletRequest
HttpSession
ServletContext
它们都有共同的属性处理方法:+ getAttribute(String name) : Object
+ getAttributeNames() : Enumeration
+ setAttribute(String name, Object value) : void
+ removeAttribute(String name): void由此可见,对于不同级别的属性,需要处理属性的添加、删除、和更新。
为此而定义了一系列属性变化能触发的事件类:ServletRequestAttributeEvent extends ServletRequestEvent
HttpSessionBindingEvent extends HttpSessionEvent
ServletContextAttributeEvent extends ServletContextEvent这里需要注意,并没有HttpSessionAttributeEvent,而是采用HttpSessionBindingEvent。
事件类层次图
如图所示是Servlet的事件层次图。其中在Attribute响应那一级中,都会有getName()和getValue()方法。
响应器:都是接口
我这里把Listener称之为响应器。所有的响应器都是接口。实现了对应的接口,就可以用来
响应某个事件了。这个概念对于有GUI编程经验的同学应该
不陌生。
响应器的鼻祖:java.util.EventListener
这是一个tagging interface。类似的tagging interface还有Serializable, Clonable.生命周期响应器
有两个,一个是针对ServletContext的初始化和Destroy的。ServletContextListener:
+ contextInitialized(ServletContextEvent event) : void
初始化后即执行
+ contextDestroyed(ServletContextEvent event) : void
准备destroy时执行
另一个是针对Session的,是创建session对象后和销毁session之前(session无效之后)执行:
HttpSessionListener
+ sessionCreated(HttpSessionEvent event) : void
+ sessionDestroyed(HttpSessionEvent event) : void
注意
如果在分布式环境中,就不要完全依靠这两个事件响应器来处理事件。
属性变化响应器
有三个,针对三个不同的共享层次:ServletContextAttributeListener
+ attributeAdded(ServletContextAttributeEvent event) : void
+ attributeRemoved(ServletContextAttributeEvent event): void
+ attributeReplaced(ServletContextAttributeEvent event): void
HttpSessionAttributeListener
+ attributeAdded(HttpSessionBindingEvent event) : void
+ attributeRemoed(HttpSessionBindingEvent event) : void
+ attributeReplaced(HttpSessionBindingEvent event) : void
ServletRequestAttributeListener
+ attributeAdded(ServletRequestAttributeEvent event) : void
+ attributeRemoved(ServletRequestAttributeEvent event) : void
+ attributeReplaced(ServletRequestAttributeEvent event) : void
注意
这面的所有listener,包括楼上谈到的生命周期listener都有两个特点:
1. 在web.xml中配置
2. 不能在分布式环境中使用(ServletRequestAttributeListener除外)
listener类层次图
如图所示是上面提到的Listener接口层次图
配置listener
在web.xml中配置listener是很简单的。<web-app>提供了<listener>子元素,它有且只有一个子元素,<listener-class>:
<web-app>
...
<listener>
<listener-class>com.key.KeyServletContextListener</listener-class>
</listener>
...
</web-app>
我们不妨看看Tomcat 6.0对于Listener配置的处理实现:3896 // Sort listeners in two arrays
3897 ArrayList eventListeners = new ArrayList();
3898 ArrayList lifecycleListeners = new ArrayList();
3899 for (int i = 0; i < results.length; i++) {
3900 if ((results instanceof ServletContextAttributeListener)
3901 || (results instanceof ServletRequestAttributeListener)
3902 || (results instanceof ServletRequestListener)
3903 || (results instanceof HttpSessionAttributeListener)) {
3904 eventListeners.add(results);
3905 }
3906 if ((results instanceof ServletContextListener)
3907 || (results instanceof HttpSessionListener)) {
3908 lifecycleListeners.add(results);
3909 }
3910 }
3911
3912 setApplicationEventListeners(eventListeners.toArray());
3913 setApplicationLifecycleListeners(lifecycleListeners.toArray());这是org.apache.catalina.core.StandardContext中的代码。系统把所有事件响应器归成两类,
一类用是普通事件响应器,另一类是lifecycle响应器,分别存入两个数组中备用。
请注意,在Line-3906行没有采用else if,而是另起一个if,这样就可以使同一个响应器,如果实现了
两类接口,可以归入两个不同类别的数组。
同一个程序文件相邻位置就有对ServletContextListener的调用:3920 Object instances[] = getApplicationLifecycleListeners();
3921 if (instances == null)
3922 return (ok);
3923 ServletContextEvent event =
3924 new ServletContextEvent(getServletContext());
3925 for (int i = 0; i < instances.length; i++) {
3926 if (instances == null)
3927 continue;
3928 if (!(instances instanceof ServletContextListener))
3929 continue;
3930 ServletContextListener listener =
3931 (ServletContextListener) instances;
3932 try {
3933 fireContainerEvent("beforeContextInitialized", listener);
3934 listener.contextInitialized(event);
3935 fireContainerEvent("afterContextInitialized", listener);
3936 } catch (Throwable t) {
3937 fireContainerEvent("afterContextInitialized", listener);
3938 getLogger().error
3939 (sm.getString("standardContext.listenerStart",
3940 instances.getClass().getName()), t);
3941 ok = false;
3942 }
3943 }以上的代码在listenerStart()方法中。在listenerStop()方法中,则有相应的contextDestroyed()方法的调用,
这里就没有细述的必要啦。
页:
[1]