key 发表于 14-7-2009 21:23:46

Listener技术在Servlet环境下的应用 —— 小key出品

在spring横行的今天,Servlet的一些基本技术可能不被人们重视。
其实在Servlet中还是有一些很有意思的技术细节的,比如listener就是
其中之一。

本文的目的是讨论一下Servlet环境下如何配置和使用相关的listener

key 发表于 14-7-2009 21:35:48

事件与java.util.EventObject

事件,Event,是一个GUI编程的概念。所谓事件驱动,是指Windows编程时,
由一些相应的事件,如鼠标操作、按钮点击等,触发的操作。

在Java编程理念中,事件是被封装成一个事件对象。这种理念从AWT界面编程开始
就已经实现在Java语言中。

于是,从那时起,就有一个作为各种事件的公共父类:EventObject

在Web编程时代,事件依然起源于EventObject。这个对象很简单,但很实用,
它有一个带参数的构造函数,以及两个方法:# source : Object
+ EventObject(Object source)

+ getSource() : Object
+ toString() : StringSource是事件源,就是由谁来触发这个事件。整个EventObject可以简单
描述成:谁做了什么 —— 这正好就是事件的意义

key 发表于 14-7-2009 21:47:02

最简单的事件:生命周期事件对象

页面、请求、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

key 发表于 14-7-2009 21:55:35

走近生命周期事件对象

从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   }

key 发表于 14-7-2009 22:02:57

属性变化而触发的事件

除了生命周期外,另一个很需要重视的事件是不同级别的属性触发的事件。

我们知道,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。

key 发表于 14-7-2009 22:07:23

事件类层次图



如图所示是Servlet的事件层次图。其中在Attribute响应那一级中,都会有getName()和getValue()方法。

key 发表于 14-7-2009 22:33:12

响应器:都是接口

我这里把Listener称之为响应器。

所有的响应器都是接口。实现了对应的接口,就可以用来
响应某个事件了。这个概念对于有GUI编程经验的同学应该
不陌生。

key 发表于 14-7-2009 22:37:13

响应器的鼻祖:java.util.EventListener

这是一个tagging interface。类似的tagging interface还有Serializable, Clonable.

key 发表于 14-7-2009 22:43:16

生命周期响应器

有两个,一个是针对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

注意
如果在分布式环境中,就不要完全依靠这两个事件响应器来处理事件。

key 发表于 14-7-2009 22:49:34

属性变化响应器

有三个,针对三个不同的共享层次:
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除外)

key 发表于 14-7-2009 22:52:56

listener类层次图



如图所示是上面提到的Listener接口层次图

key 发表于 14-7-2009 23:23:06

配置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]
查看完整版本: Listener技术在Servlet环境下的应用 —— 小key出品