应用事件设施给Web应用开发人员更好地控制ServletContext、HttpSession和ServletRequest的生命周期,可以更好地分解代码,并提高了管理Web应用使用的资源的效率。
应用事件监听器是实现了一个或多个Servlet事件监听器接口的类。它们在部署Web应用时,实例化并注册到Web容器中。它们由开发人员在WAR包中提供。
Servlet事件监听器支持在ServletContext、HttpSession和ServletRequest状态改变时进行事件通知。Servlet上下文监听器是用来管理资源或把应用的状态保持在一个JVM级别。HTTP会话监听器是用来管理从相同客户端或用户进入web应用的一系列与请求关联的状态或资源。Servlet请求监听器是用来管理整个Servlet请求生命周期的状态。异步监听器是用来管理异步事件,例如超时和完成异步处理。
可以有多个监听器类监听每一个事件类型,且开发人员可以为每一个事件类型指定容器调用监听器bean的顺序。
事件类型和监听器接口用于监控下表所示的事件:
表 11-1 Servlet上下文事件
事件类型 |
描述 |
监听器接口 |
生命周期 |
Servlet上下文刚刚创建 并可用于服务它的第一个请求, 或者Servlet上下文即将关闭。 |
javax.servlet. ServletContextListener |
更改属性 |
在Servlet上下文的属性已添加、删除、或替换。 |
javax.servlet. ServletContextAttributeListener |
表 11-2 HTTP会话事件
事件类型 |
描述 |
监听器接口 |
生命周期 |
会话已创建、无效或超时。 |
javax.servlet.http. HttpSessionListener |
更改属性 |
已经在HttpSession上添加、移除、或替换属性。 |
javax.servlet.http. HttpSessionAttributeListener |
会话迁移 |
HttpSession已被激活或钝化。 |
javax.servlet.http. HttpSessionActivationListener |
对象绑定 |
对象已经从HttpSession绑定或解除绑定 |
javax.servlet.http. HttpSessionBindingListener |
表 11-3 Servlet请求事件
事件类型 |
描述 |
监听器接口 |
生命周期 |
一个servlet请求已经开始由Web组件处理。 |
javax.servlet. ServletRequestListener |
更改属性 |
已经在ServletRequest上添加、移除、或替换属性。 |
javax.servlet. ServletRequestAttributeListener |
异步事件 |
超时、连接终止或完成异步处理 |
javax.servlet.AsyncListener |
这些API的详细信息请参考API文档。
为了说明事件使用方案,考虑一个包含一些使用数据库的Servlet的简单Web应用。开发人员提供了一个Servlet上下文监听器类来管理数据库连接。
1. 当应用启动时,监听器类得到通知。应用登录到数据库,并在servlet上下文中存储连接。
2. 应用中的Servlet根据需要,在Web应用的活动期间访问连接。
3. 当Web服务器关闭时,或应用从Web服务器移除时,监听器类得到通知并关闭数据库连接。
Web应用的开发人员提供实现了一个或多个在javax.servlet API中的监听器接口的监听器类。每一个监听器类必须有一个公共的无参构造器。监听器类打包到WAR包中,或者在WEB-INF/classes归档项下,或者在WEB-INF/lib目录的一个JAR内部。
监听器类在Web应用部署描述符中使用listener元素声明。它们根据类名列出的顺序就是它们被调用的顺序。与其他监听器不同,AsyncListener类型的监听器可能仅通过编程式注册(使用一个ServletRequest)。
Web容器为每一个监听器类创建一个实例,并在应用处理第一个请求之前为事件通知注册它。Web容器根据他们实现的接口和它们出现在部署描述中的顺序注册监听器实例。在Web应用执行期间,监听器按照它们注册的顺序进行调用。
在应用关闭时,监听器以它们声明时相反的顺序得到通知,且通知会话监听器在通知上下文监听器之前。应用关闭时会话监听器必须在上下文监听器接收到通知之前接收到会话失效通知。
以下示例是注册两个Servlet上下文生命周期监听器和一个HttpSession监听器的部署语法。
假设com.acme.MyConnectionManager和com.acme.MyLoggingModule两个都实现了javax.servlet.ServletContextListener,且com.acme.MyLoggingModule又实现了javax.servlet.http.HttpSessionListener。此外,开发人员希望com.acme.MyConnectionManager在com.acme.MyLoggingModule之前得到Servlet上下文生命周期事件的通知。下面是这个应用的部署描述符:
<web-app> <display-name>MyListeningApplication</display-name> <listener> <listener-class>com.acme.MyConnectionManager</listener-class> </listener> <listener> <listener-class>com.acme.MyLoggingModule</listener-class> </listener> <servlet> <display-name>RegistrationServlet</display-name> ...etc </servlet> </web-app>
容器需要在开始执行进入应用的第一个请求之前完成Web应用中的监听器类的实例化。容器必须保持每一个监听器的引用直到为Web应用最后一个请求提供完服务。
ServletContext和HttpSession对象的属性改变可能会同时发生。不要求容器同步到属性监听器类产生的通知。维护状态的监听器类负责数据的完整性且应明确处理这种情况。
一个监听器里面的应用代码在运行期间可能会抛出异常。一些监听器通知发生在应用中的另一个组件调用树过程中。这方面的一个例子是一个Servlet设置了会话属性,该会话监听器抛出一个未处理异常。容器必须允许未处理的异常由描述在10.9节“错误处理”的错误页面机制处理。如果没有为这些异常指定错误页面,容器必须确保返回一个状态码为500的响应。这种情况下,将不再调用这种事件的监听器。
有些异常不会发生在应用中的另一个组件调用栈过程中。这方面的一个例子是SessionListener接收会话已经超时的通知并抛出未处理的异常,或者ServletContextListener在Servlet上下文初始化通知期间抛出未处理异常,或者ServletRequestListener在初始化或销毁请求对象的通知期间抛出未处理异常。这种情况下,开发人员没有机会处理这种异常。容器可以为所有后续到Web应用的请求返回HTTP状态码500来表示应用出错了。
如果开发人员希望在监听器产生一个异常后还能正常处理,那么它必须在通知方法中处理它们自己的异常。
在分布式Web容器中,HttpSession实例被限定到特定的JVM服务会话请求作用域内,且ServletContext对象被限定到Web容器所在的JVM中。分布式容器不需要传播Servlet上下文事件或HttpSession事件到其他JVM。监听器类实例被限定到每个JVM的每个部署描述符声明中。
监听器类提供给开发人员一种跟踪Web应用内会话的方式。它通常在跟踪会话来知道是否因为容器使会话超时或应用中的Web组件调用了invalidate方法导致这个会话失效时很有用。该区别可能会间接地决定使用监听器和HttpSessionAPI方法。