Servlet API提供了一系列的事件和事件监听接口。 上层的servlet/JSP应用能够通过调用这些API进行事件 驱动的开发。这里监听的所有事件都继承自 java.util.Event对象。监听器接口可以分为三类: ServletContext、HttpSession 和ServletRequest 。
一.监听器接口和注册
1. 监听器接口主要在 javax.servlet 和javax.servlet.http 的包中。有以下这些接口:
- javax.servlet.ServletContextListener:它能够响应 ServletContext生命周期事件,它提供了 ServletContext创建之后和ServletContext关闭之前的 会被调用的方法。
- javax.servlet.ServletContextAttributeListener:它能够 响应ServletContext范围的属性添加、删除、替换事 件。
- javax.servlet.http.HttpSessionListener:它能够响应 HttpSession的创建、超时和失效事件
- javax.servlet.http.HttpSessionAttributeListener:它能 响应HttpSession范围的属性添加、删除、替换事 件。
- javax.servlet.http.HttpSessionActivationListener:它在 一个HttpSession激活或者失效时被调用。
- javax.servlet.http.HttpSessionBindingListener:可以 实现这个接口来保存HttpSession范围的属性。当有 属性从HttpSession添加或删除时,
- HttpSessionBindingListener 接口能够做出响应。
- javax.servlet.ServletRequestListener:它能够响应一 个ServletRequest的创建或删除。
- javax.servlet.ServletRequestAttributeListener:它能响 应ServletRequest范围的属性值添加、删除、修改事 件。
- javax.servlet.AsyncListener:一个用于异步操作的监 听器,在第11章会进行更详细的介绍。
2. 编写一个监听器,只需要写一个Java类来实现对应 的监听器接口就可以了。在Servlet 3.0和Servlet 3.1中提 供了两种注册监听器的方法。
第一种是使用 WebListener注解。例如:
WebListenerpublic class ListenerClass implements ListenerInterface {}
第二种方法是在部署描述文档中增加一个listener元 素。
fully-qualified listener class
你可以在一个应用中添加多个监听器,这些监听器 是同步工作的。
二.Servlet Context 监听器
ServletContext的监听器接口有两个: ServletContextListener和 ServletContextAttributeListener。
1. ServletContextListener
ServletContextListener能对ServletContext的创建和 销毁做出响应。当ServletContext初始化时,容器会调用 所有注册的ServletContextListeners的contextInitialized 方 法。该方法如下:
void contextInitialized(ServletContextEvent event)
当ServletContext将要销毁时,容器会调用所有注册 的ServletContextListeners的context Destroyed 方法。该 方法如下:
void contextDestroyed(ServletContextEvent event)
contextInitialized 方法和contextDestroyed方法都会 从容器获取到一个 ServletContextEvent。 javax.servlet.ServletContextEvent是一个 java.util.EventObject的子类 ,它定义了一个访问 ServletContext的getServletContext 方法,通过这个方法能够轻松地获取到ServletContext。:
ServletContext getServletContext()
例: AppListener类实现了ServletContextListener 接口,它在 ServletContext刚创建时,将一个保存国家编码和国家名 的Map放置到ServletContext中。
AppListener类
package listener;import java.util.HashMap;import java.util.Map;import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;@WebListenerpublic class AppListener implements ServletContextListener{ @Override public void contextDestroyed(ServletContextEvent sce) { } @Override public void contextInitialized(ServletContextEvent sce) { ServletContext servletContext = sce.getServletContext(); Mapcountries = new HashMap () ; countries.put("ca", "Cannada"); countries.put("us","United States"); servletContext.setAttribute("countries",countries); }}
注意,contextInitialized 方法。它 通过调用getServletContext方法从容器获得了 ServletContext,然后创建了一个Map用于保存国家编码 和国家名,再将这个Map放置到ServletContext里。在实 际开发中,往往是把数据库里的数据放置到 ServletContext里。
countries.jsp用到了这个监听器。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>Country list we operate in these countries:
- ${country.value}
结果(resulte)
2.ServletContextAttributeListener
当一个ServletContext范围的属性被添加、删除或者 替换时,ServletContextAttributeListener接口的实现类会 接收到消息。这个接口定义了如下三个方法:
void attributeAdded(ServletContextAttributeEvent event)void attributeRemoved(ServletContextAttributeEvent event)void attributeReplaced(ServletContextAttributeEvent event)
attributeAdded方法在一个ServletContext范围属性被 添加时被容器调用。attributeRemoved方法在一个 ServletContext范围属性被删除时被容器调用。而 attributeReplaced方法在一个ServletContext范围属性被 新的替换时被容器调用。
这三个方法都能获取到一个 ServletContextAttributeEvent的对象,通过这个对象可以 获取属性的名称和值。
ServletContextAttributeEvent类继承自 ServletContextAttribute,并且增加了下面两个方法分别 用于获取该属性的名称和值:
java.lang.String getName()java.lang.Object getValue()
例
servlet页面
package listener;import javax.servlet.ServletContextAttributeListener;import javax.servlet.annotation.WebListener;import javax.servlet.ServletContextAttributeEvent;import javax.servlet.ServletContext;@WebListenerpublic class SCAlistener implements ServletContextAttributeListener{ @Override public void attributeAdded(ServletContextAttributeEvent event) { String name = event.getName(); Object value = event.getValue(); System.out.println(name + " create success, value " + value); }}
三. Session Listeners
一共有四个HttpSession相关的监听器接口: HttpSessionListener,HttpSessionActivation Listener、 HttpSessionAttributeListener和 HttpSessionBindingListener。这四个接口都在 javax.servlet.http包中,下面分别对它们进行介绍。
1. HttpSessionListener
当一个HttpSession创建或者销毁时,容器都会通知 所有的HttpSessionListener监听器,HttpSessionListener 接口有两个方法:sessionCreated和sessionDestroyed:
void sessionCreated(HttpSessionEvent event)void sessionDestroyed(HttpSessionEvent event)
这两个方法都可以接收到一个继承于java.util.Event 的HttpSessionEvent对象。可以通过调用 HttpSessionEvent对象的getSession方法来获取当前的 HttpSession。getSession方法如下:
HttpSession getSession()
举一个例子,app08a应用中的 SessionListener类。这个监听器来统计HttpSession的数 量。它使用了一个AtomicInteger对象来统计,并且将这 个对象保存成ServletContext范围的属性。每当有一个 HttpSession被创建时,这个AtomicInteger对象就会加 一。每当有一个HttpSession被销毁时,这个 AtomicInteger对象就会减一。所以这个对象会保存着当 前存活的HttpSession数量。这里使用了AtomicInteger 来 代替Integer类型是为了保证能同步进行加减的操作。
SessionListener 类
package listener;import java.util.concurrent.atomic.AtomicInteger;import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;import javax.servlet.http.HttpSession;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;@WebListenerpublic class SessionListener implements HttpSessionListener, ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { ServletContext servletContext = sce.getServletContext(); servletContext.setAttribute("userCounter", new AtomicInteger()); } @Override public void contextDestroyed(ServletContextEvent sce) { } @Override public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); ServletContext servletContext = session.getServletContext(); AtomicInteger userCounter = (AtomicInteger) servletContext.getAttribute("userCounter"); int userCount = userCounter.incrementAndGet(); System.out.println("userCount incremented to :" + userCount); } @Override public void sessionDestroyed(HttpSessionEvent se) { HttpSession session = se.getSession(); ServletContext servletContext = session.getServletContext(); AtomicInteger userCounter = (AtomicInteger) servletContext.getAttribute("userCounter"); int userCount = userCounter.decrementAndGet(); System.out.println("---------- userCount decremented to:" + userCount); }}
如清单8.3所示,SessionListener类实现了 ServletContextListener和HttpSessionListener接口。所以 需要实现这两个接口的所有方法。
其中继承自ServletContextListener接口的 contextInitialized方法创建了一个AtomicInteger对象并将 其保存在ServletContext属性中。由于是在应用启动的时 候创建,因此这个AtomicInteger对象的初始值为0。这 个ServletContext属性的名字为userCounter。
sessionCreated方法在每个HttpSession创建时被调 用。当有HttpSession创建时,从ServletContext中获取 userCounter属性。然后调用userCounter的 incrementAndGet 方法让计数加一。最后在控制台将 userCounter的值打印出来,可以直观地看到效果。
sessionDestroyed方法会在HttpSession销毁之前被调 用。这个方法的实现和sessionCreated类似,只不过对 userCounter改为减一操作。
可以通过不同的浏览器访问countries.jsp页面来查 看监听器的效果,
用同一个浏览器再次访问这个URL并不会改变 userCounter,因为这属于同一个HttpSession。使用不同 的浏览器访问才能增加userCounter的值。
如果你有时间等待HttpSession过期的话,在控制台 也能看到HttpSession销毁时打印的信息。
2. HttpSessionAttributeListener
HttpSessionAttributeListener接口和 ServletContextAttributeListener类似,它响应的是 HttpSession范围属性的添加、删除和替换。
HttpSessionAttributeListener接口有以下方法
void attributeAdded(HttpSessionBindingEvent event)void attributeRemoved( HttpSessionBindingEvent event)void attributeReplaced( HttpSessionBindingEvent event)
attributeAdded方法在一个HttpSession范围属性被添 加时被容器调用。attributeRemoved方法在一个 HttpSession范围属性被删除时被容器调用。而 attributeReplaced方法在一个HttpSession范围属性被新的 替换时被容器调用.
这三个方法都能获取到一个 HttpSessionBindingEvent 的对象,通过这个对象可以获 取属性的名称和值:
java.lang.String getName()java.lang.Object getValue()
由于HttpSessionBindingEvent是HttpSessionEvent的 子类,因此也可以在HttpSession Attribute Listener 实现 类中获得HttpSession。
例
servlet页面
@WebListenerpublic class SessionListener implements HttpSessionListener, ServletContextListener ,HttpSessionAttributeListener{ @Override public void attributeAdded(HttpSessionBindingEvent event) { String name = event.getName(); Object value = event.getValue(); System.out.println("name = " + name + ", value is " + value); }
3. HttpSessionActivationListener
在分布式环境下,会用多个容器来进行负载均衡, 有可能需要将session保存起来,在容器之间传递。例如 当一个容器内存不足时,会把很少用到的对象转存到其 他容器上。这时候,容器就会通知所有 HttpSessionActivationListener 接口的实现类。
HttpSessionActivationListener接口有两个方法, sessionDidActivate和sessionWillPassivate:
void sessionDidActivate(HttpSessionEvent event)void sessionWillPassivate(HttpSessionEvent event)
当HttpSession被转移到其他容器之后, sessionDidActivate方法会被调用。容器将一个 HttpSessionEvent方法传递到方法里,可以从这个对象 获得HttpSession。
当一个HttpSession将要失效时,容器会调用 sessionWillPassivate方法。和sessionDidActivate方法一 样,容器将一个HttpSessionEvent方法传递到方法里, 可以从这个对象获得HttpSession。
4. HttpSessionBindingListener
当有属性绑定或者解绑到HttpSession上时, HttpSessionBindingListener 监听器会被调用。如果对 HttpSession属性的绑定和解绑动作感兴趣,就可以实现 HttpSessionBindingListener 来监听。例如可以在 HttpSession属性绑定时更新状态,或者在属性解绑时释 放资源。
Product类就是一个例子。
HttpSessionBindingListener的一个实现类
package model;import javax.servlet.http.HttpSessionBindingEvent;import javax.servlet.http.HttpSessionBindingListener;public class Product implements HttpSessionBindingListener { private String id; private String name; private double price; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } @Override public void valueBound(HttpSessionBindingEvent event) { //当有HttpSessionBindingEvent 发生时会自动调用 String attributeName = event.getName(); System.out.println(attributeName + " valueBound"); } @Override public void valueUnbound(HttpSessionBindingEvent event) { String attributeName = event.getName(); System.out.println(attributeName + " valueUnbound"); }}
jsp页面
<% model.Product product = new Product(); product.setId("1"); product.setName("d"); product.setPrice(1.5); session.setAttribute("Product",product);%>
四. ServletRequest Listeners
ServletRequest范围的监听器接口有三个: ServletRequestListener、ServletRequestAttribute Listener 和AsyncListener。
1. ServletRequestListener
ServletRequestListener监听器会对ServletRequest的 创建和销毁事件进行响应。容器会通过一个池来存放并 重复利用多个ServletRequest,ServletRequest的创建是 从容器池里被分配出来的时刻开始,而它的销毁时刻是 放回容器池里的时间。
ServletRequestListener 接口有两个方法, requestInitialized和requestDestroyed:
void requestInitialized(ServletRequestEvent event)void requestDestroyed(ServletRequestEvent event)
当一个ServletRequest创建(从容器池里取出) 时,requestInitialized方法会被调用,当ServletRequest销 毁(被容器回收)时,requestDestroyed方法会被调用。 这两个方法都会接收到一个ServletRequestEvent对象, 可以通过使用这个对象的getServletRequest方法来获取ServletRequest对象:
ServletRequest getServletRequest()
另外,ServletRequestEvent接口也提供了一个 getServletContext方法来获取ServletContext,如下所 示:
ServletContext getServletContext()
例: PerfStatListener类。这个监听器用来计算每个ServletRequest从创建到销毁的生存时间。
PerfStatListener实现了 ServletRequestListener接口,来计算每个HTTP请求的完 成时间。由于容器在请求创建时会调用 ServletRequestListener的requestInitialized方法,在销毁 时会调用requestDestroyed,因此很容易就可以计算出时 间。只需要在记录下两个事件的事件,并且相减,就可 以计算出一次HTTP请求的完成时间了。
PerfStatListener类
package listener;import javax.servlet.ServletRequest;import javax.servlet.ServletRequestEvent;import javax.servlet.ServletRequestListener;import javax.servlet.annotation.WebListener;import javax.servlet.http.HttpServletRequest;@WebListenerpublic class PerfStatListener implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent sre) { ServletRequest servletRequest = sre.getServletRequest(); servletRequest.setAttribute("start", System.nanoTime()); } @Override public void requestDestroyed(ServletRequestEvent sre) { ServletRequest servletRequest = sre.getServletRequest(); Long start = (Long) servletRequest.getAttribute("start"); Long end = System.nanoTime(); HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String uri = httpServletRequest.getRequestURI(); System.out.println("time taken to execute " + uri + ":" + ((end - start) / 1000) + "microseconds"); }}
requestInitialized 方法调用 System.nanoTime()获取当前系统时间的数值(Long类 型),并将这个数值保存到ServletRequest中.
nanoTime返回一个long类型的数值来表示任意时 间。这个数值和系统或是时钟时间都没什么关系,但是 同一个JVM上调用两次nanoTime得到的数值可以计算出两次调用之间的时间。
所以,在requestDestroyed方法中再次调用 nanoTime方法,并且减去第一次调用获得的数值,就 得到HTTP请求的完成时间了:
效果
2. ServletRequestAttributeListener
当一个ServletRequest范围的属性被添加、删除或 替换时,ServletRequestAttributeListener接口会被调用。 ServletRequestAttributeListener接口提供了三个方法: attributeAdded、attribute Replaced和attributeRemoved。 如下所示:
void attributeAdded(ServletRequestAttributeEvent event)void attributeRemoved(ServletRequestAttributeEvent event)void attributeReplaced(ServletRequestAttributeEvent event)
这些方法都可以获得一个继承自 ServletRequestEvent的ServletRequestAttributeEvent对 象。通过ServletRequestAttributeEvent类提供的getName 和getValue方法可以访问到属性的名称和值:
java.lang.String getName()java.lang.Object getValue()
例;
@WebListenerpublic class SessionListener implements HttpSessionListener, ServletContextListener ,HttpSessionAttributeListener, ServletRequestAttributeListener{ @Override public void attributeAdded(ServletRequestAttributeEvent event) { System.out.println("I was called............"); }