5.3 Tomcat Listener (监听器)


文档摘要

5.3 Tomcat Listener (监听器) 5.3 Tomcat Listener (监听器) 5.3.1 监听器概述 Tomcat 作为 Servlet 容器,遵循 Servlet 规范,并在此基础上进行了扩展和增强。监听器正是 Servlet 规范中定义的一种机制,它允许 Web 应用在特定的事件发生时得到通知并做出响应。这些事件通常与 Web 应用的生命周期、会话的生命周期、以及请求的处理过程相关。 什么是监听器? 简单来说,监听器是实现了特定接口的 Java 类,这些接口定义了在特定事件发生时需要执行的方法。当 Tomcat 容器检测到预定义的事件发生时,它会调用注册到该事件的监听器中相应的方法,从而触发开发者预先设定的逻辑。

5.3 Tomcat Listener (监听器)

5.3 Tomcat Listener (监听器)

5.3.1 监听器概述

Tomcat 作为 Servlet 容器,遵循 Servlet 规范,并在此基础上进行了扩展和增强。监听器正是 Servlet 规范中定义的一种机制,它允许 Web 应用在特定的事件发生时得到通知并做出响应。这些事件通常与 Web 应用的生命周期、会话的生命周期、以及请求的处理过程相关。

什么是监听器?

简单来说,监听器是实现了特定接口的 Java 类,这些接口定义了在特定事件发生时需要执行的方法。当 Tomcat 容器检测到预定义的事件发生时,它会调用注册到该事件的监听器中相应的方法,从而触发开发者预先设定的逻辑。

监听器的作用

监听器在 Web 应用中扮演着“观察者”的角色,它们的主要作用包括:

  • 生命周期管理: 监听 Web 应用、会话、请求的创建、初始化、销毁等生命周期事件,方便进行资源的初始化和清理工作。例如,在 Web 应用启动时加载配置信息,在会话创建时分配用户资源,在请求结束时记录日志。

  • 事件驱动编程: 通过监听事件,实现事件驱动的编程模型,使得应用能够对特定事件做出实时响应,增强应用的灵活性和响应性。

  • 扩展容器功能: 通过自定义监听器,可以扩展 Tomcat 容器的功能,例如实现自定义的会话管理、安全审计、性能监控等。

  • 解耦合: 监听器将事件的产生者和处理者解耦,使得业务逻辑与容器事件处理逻辑分离,提高代码的可维护性和可重用性。

监听器与事件

监听器与事件是紧密相关的。Tomcat 容器中存在多种类型的事件,每种事件都对应着一组监听器接口。当某种事件发生时,Tomcat 会遍历所有注册到该事件的监听器,并依次调用监听器中对应的方法。

例如,当 ServletContext (Web 应用上下文) 被创建时,会触发 ServletContextEvent 事件,而实现了 ServletContextListener 接口的监听器就会接收到这个事件通知,并执行 contextInitialized() 方法。

5.3.2 监听器类型详解

Tomcat 提供了多种类型的监听器,它们分别监听不同类型的事件,并提供不同的接口供开发者实现。根据监听的事件类型,可以将 Tomcat Listener 主要分为以下几类:

  • ServletContext 监听器

  • HttpSession 监听器

  • ServletRequest 监听器

下面我们将逐一详细介绍这些监听器类型,并给出相应的代码示例。

5.3.2.1 ServletContext 监听器

ServletContext 监听器用于监听 ServletContext 对象的生命周期事件以及属性变更事件。ServletContext 代表着整个 Web 应用的上下文,它在 Web 应用启动时被创建,在 Web 应用停止时被销毁。

ServletContext 监听器主要包括以下两个接口:

  • ServletContextListener: 监听 ServletContext 的生命周期事件,即创建和销毁。

  • ServletContextAttributeListener: 监听 ServletContext 属性的变更事件,即属性的添加、替换和移除。

1. ServletContextListener

ServletContextListener 接口定义了两个方法:

  • contextInitialized(ServletContextEvent sce): 当 ServletContext 初始化完成时 (通常在 Web 应用启动时) 被调用。

  • contextDestroyed(ServletContextEvent sce): 当 ServletContext 即将被销毁时 (通常在 Web 应用停止时) 被调用。

代码示例:

package com.example.listener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class MyServletContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("ServletContext 初始化完成!"); // 在 Web 应用启动时执行的初始化操作,例如加载配置信息、初始化数据库连接池等 String contextPath = sce.getServletContext().getContextPath(); System.out.println("Web 应用上下文路径: " + contextPath); sce.getServletContext().setAttribute("appStartTime", System.currentTimeMillis()); // 设置应用启动时间属性 } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("ServletContext 即将被销毁!"); // 在 Web 应用停止时执行的清理操作,例如释放资源、关闭连接池等 long startTime = (long) sce.getServletContext().getAttribute("appStartTime"); long endTime = System.currentTimeMillis(); System.out.println("Web 应用运行时间: " + (endTime - startTime) + "ms"); } }

配置 ServletContextListener:

web.xml 文件中配置 ServletContextListener

<listener> <listener-class>com.example.listener.MyServletContextListener</listener-class> </listener>

Mermaid 图示:ServletContextListener 生命周期

2. ServletContextAttributeListener

ServletContextAttributeListener 接口定义了三个方法,用于监听 ServletContext 属性的变更:

  • attributeAdded(ServletContextAttributeEvent scae): 当向 ServletContext 添加新的属性时被调用。

  • attributeRemoved(ServletContextAttributeEvent scae): 当从 ServletContext 移除属性时被调用。

  • attributeReplaced(ServletContextAttributeEvent scae): 当替换 ServletContext 中已存在的属性时被调用。

代码示例:

package com.example.listener; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; public class MyServletContextAttributeListener implements ServletContextAttributeListener { @Override public void attributeAdded(ServletContextAttributeEvent scae) { System.out.println("ServletContext 属性添加:name=" + scae.getName() + ", value=" + scae.getValue()); } @Override public void attributeRemoved(ServletContextAttributeEvent scae) { System.out.println("ServletContext 属性移除:name=" + scae.getName() + ", value=" + scae.getValue()); } @Override public void attributeReplaced(ServletContextAttributeEvent scae) { System.out.println("ServletContext 属性替换:name=" + scae.getName() + ", oldValue=" + scae.getValue() + ", newValue=" + scae.getServletContext().getAttribute(scae.getName())); } }

配置 ServletContextAttributeListener:

web.xml 文件中配置 ServletContextAttributeListener

<listener> <listener-class>com.example.listener.MyServletContextAttributeListener</listener-class> </listener>

应用场景:ServletContext 监听器

  • 资源初始化和清理:contextInitialized() 中初始化数据库连接池、加载配置文件、初始化缓存等,在 contextDestroyed() 中释放这些资源,保证应用的正常运行和优雅关闭。

  • 应用启动时间记录: 使用 ServletContextListener 记录应用启动时间,方便进行性能分析和监控。

  • 全局配置管理: 通过 ServletContext 存储全局配置信息,并在 ServletContextAttributeListener 中监听配置变更事件,动态更新应用配置。

  • 系统监控: 监听 ServletContext 的生命周期事件,可以实现简单的系统启动和停止监控。

5.3.2.2 HttpSession 监听器

HttpSession 监听器用于监听 HttpSession 对象的生命周期事件以及属性变更事件。HttpSession 代表着用户的会话,用于跟踪用户的状态。

HttpSession 监听器主要包括以下几个接口:

  • HttpSessionListener: 监听 HttpSession 的生命周期事件,即创建和销毁。

  • HttpSessionAttributeListener: 监听 HttpSession 属性的变更事件,即属性的添加、替换和移除。

  • HttpSessionActivationListener: 监听 HttpSession 的钝化 (passivation) 和激活 (activation) 事件,用于在会话被序列化和反序列化时执行特定操作 (例如集群环境下的会话复制)。

  • HttpSessionBindingListener: 允许对象自身监听被绑定到或解除绑定到 HttpSession 的事件,用于对象自身管理会话绑定状态。

1. HttpSessionListener

HttpSessionListener 接口定义了两个方法:

  • sessionCreated(HttpSessionEvent hse): 当新的 HttpSession 被创建时被调用 (例如,用户第一次访问网站并创建会话)。

  • sessionDestroyed(HttpSessionEvent hse): 当 HttpSession 即将被销毁时被调用 (例如,会话超时、用户显式注销、Web 应用关闭等)。

代码示例:

package com.example.listener; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class MyHttpSessionListener implements HttpSessionListener { private static int activeSessions = 0; @Override public void sessionCreated(HttpSessionEvent hse) { System.out.println("HttpSession 创建:ID=" + hse.getSession().getId()); activeSessions++; System.out.println("当前活跃会话数: " + activeSessions); } @Override public void sessionDestroyed(HttpSessionEvent hse) { System.out.println("HttpSession 销毁:ID=" + hse.getSession().getId()); activeSessions--; System.out.println("当前活跃会话数: " + activeSessions); } }

配置 HttpSessionListener:

web.xml 文件中配置 HttpSessionListener

<listener> <listener-class>com.example.listener.MyHttpSessionListener</listener-class> </listener>

Mermaid 图示:HttpSessionListener 生命周期

2. HttpSessionAttributeListener

HttpSessionAttributeListener 接口定义了三个方法,用于监听 HttpSession 属性的变更:

  • attributeAdded(HttpSessionBindingEvent hbe): 当向 HttpSession 添加新的属性时被调用。

  • attributeRemoved(HttpSessionBindingEvent hbe): 当从 HttpSession 移除属性时被调用。

  • attributeReplaced(HttpSessionBindingEvent hbe): 当替换 HttpSession 中已存在的属性时被调用。

代码示例:

package com.example.listener; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener { @Override public void attributeAdded(HttpSessionBindingEvent hbe) { System.out.println("HttpSession 属性添加:sessionID=" + hbe.getSession().getId() + ", name=" + hbe.getName() + ", value=" + hbe.getValue()); } @Override public void attributeRemoved(HttpSessionBindingEvent hbe) { System.out.println("HttpSession 属性移除:sessionID=" + hbe.getSession().getId() + ", name=" + hbe.getName() + ", value=" + hbe.getValue()); } @Override public void attributeReplaced(HttpSessionBindingEvent hbe) { System.out.println("HttpSession 属性替换:sessionID=" + hbe.getSession().getId() + ", name=" + hbe.getName() + ", oldValue=" + hbe.getValue() + ", newValue=" + hbe.getSession().getAttribute(hbe.getName())); } }

配置 HttpSessionAttributeListener:

web.xml 文件中配置 HttpSessionAttributeListener

<listener> <listener-class>com.example.listener.MyHttpSessionAttributeListener</listener-class> </listener>

3. HttpSessionActivationListener

HttpSessionActivationListener 接口定义了两个方法,用于监听 HttpSession 的钝化和激活事件。钝化是指将会话序列化到磁盘或数据库等持久化存储介质,激活是指从持久化存储介质反序列化会话到内存。这通常发生在集群环境下,为了实现会话的复制和故障转移。

  • sessionWillPassivate(HttpSessionEvent hse): 当 HttpSession 即将被钝化时被调用。

  • sessionDidActivate(HttpSessionEvent hse): 当 HttpSession 被激活时被调用。

代码示例:

package com.example.listener; import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionEvent; public class MyHttpSessionActivationListener implements HttpSessionActivationListener { @Override public void sessionWillPassivate(HttpSessionEvent hse) { System.out.println("HttpSession 即将被钝化:ID=" + hse.getSession().getId()); // 在会话钝化前执行的操作,例如释放内存资源、保存临时数据等 } @Override public void sessionDidActivate(HttpSessionEvent hse) { System.out.println("HttpSession 被激活:ID=" + hse.getSession().getId()); // 在会话激活后执行的操作,例如恢复内存资源、加载临时数据等 } }

配置 HttpSessionActivationListener:

web.xml 文件中配置 HttpSessionActivationListener

<listener> <listener-class>com.example.listener.MyHttpSessionActivationListener</listener-class> </listener>

4. HttpSessionBindingListener

HttpSessionBindingListener 接口允许对象自身监听被绑定到或解除绑定到 HttpSession 的事件。如果一个类实现了 HttpSessionBindingListener 接口,当该类的对象被 setAttribute() 方法绑定到 HttpSession 时,valueBound(HttpSessionBindingEvent event) 方法会被调用;当该对象被 removeAttribute() 方法或会话失效解除绑定时,valueUnbound(HttpSessionBindingEvent event) 方法会被调用。

  • valueBound(HttpSessionBindingEvent event): 当对象被绑定到 HttpSession 时被调用。

  • valueUnbound(HttpSessionBindingEvent event): 当对象从 HttpSession 解除绑定时被调用。

代码示例:

package com.example.listener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; public class MySessionBoundObject implements HttpSessionBindingListener { private String name; public MySessionBoundObject(String name) { this.name = name; } @Override public void valueBound(HttpSessionBindingEvent event) { System.out.println("对象被绑定到 HttpSession:name=" + name + ", sessionID=" + event.getSession().getId()); } @Override public void valueUnbound(HttpSessionBindingEvent event) { System.out.println("对象从 HttpSession 解除绑定:name=" + name + ", sessionID=" + event.getSession().getId()); } public String getName() { return name; } }

在 Servlet 中使用 HttpSessionBindingListener 对象:

// ... in a Servlet ... HttpSession session = request.getSession(); MySessionBoundObject boundObject = new MySessionBoundObject("testObject"); session.setAttribute("boundObject", boundObject); // 触发 valueBound() session.removeAttribute("boundObject"); // 触发 valueUnbound() session.invalidate(); // 触发 valueUnbound() (如果对象仍然绑定在会话中)

应用场景:HttpSession 监听器

  • 在线用户统计: 使用 HttpSessionListener 监听会话的创建和销毁,统计在线用户数量。

  • 用户会话管理:sessionCreated() 中分配用户会话相关的资源,在 sessionDestroyed() 中释放这些资源。

  • 用户行为跟踪: 监听会话的创建、销毁以及属性变更事件,记录用户的行为轨迹,用于用户行为分析和个性化推荐。

  • 集群环境下的会话管理: HttpSessionActivationListener 在集群环境中用于会话复制和故障转移,保证会话的高可用性。

  • 资源绑定和解绑: HttpSessionBindingListener 允许对象自身感知会话绑定状态,方便对象管理自身在会话中的生命周期。

5.3.2.3 ServletRequest 监听器

ServletRequest 监听器用于监听 ServletRequest 对象的生命周期事件以及属性变更事件。ServletRequest 代表着客户端的请求。

ServletRequest 监听器主要包括以下两个接口:

  • ServletRequestListener: 监听 ServletRequest 的生命周期事件,即创建和销毁。

  • ServletRequestAttributeListener: 监听 ServletRequest 属性的变更事件,即属性的添加、替换和移除。

1. ServletRequestListener

ServletRequestListener 接口定义了两个方法:

  • requestInitialized(ServletRequestEvent sre): 在 Servlet 容器即将开始处理请求时被调用 (在请求进入 Servlet 之前)。

  • requestDestroyed(ServletRequestEvent sre): 在 Servlet 容器完成处理请求之后被调用 (在请求离开 Servlet 之后)。

代码示例:

package com.example.listener; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; public class MyServletRequestListener implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("ServletRequest 初始化:URL=" + ((HttpServletRequest) sre.getServletRequest()).getRequestURL()); sre.getServletRequest().setAttribute("requestStartTime", System.currentTimeMillis()); // 记录请求开始时间 } @Override public void requestDestroyed(ServletRequestEvent sre) { System.out.println("ServletRequest 销毁:URL=" + ((HttpServletRequest) sre.getServletRequest()).getRequestURL()); long startTime = (long) sre.getServletRequest().getAttribute("requestStartTime"); long endTime = System.currentTimeMillis(); System.out.println("请求处理时间: " + (endTime - startTime) + "ms"); } }

配置 ServletRequestListener:

web.xml 文件中配置 ServletRequestListener

<listener> <listener-class>com.example.listener.MyServletRequestListener</listener-class> </listener>

Mermaid 图示:ServletRequestListener 生命周期

2. ServletRequestAttributeListener

ServletRequestAttributeListener 接口定义了三个方法,用于监听 ServletRequest 属性的变更:

  • attributeAdded(ServletRequestAttributeEvent srae): 当向 ServletRequest 添加新的属性时被调用。

  • attributeRemoved(ServletRequestAttributeEvent srae): 当从 ServletRequest 移除属性时被调用。

  • attributeReplaced(ServletRequestAttributeEvent srae): 当替换 ServletRequest 中已存在的属性时被调用。

代码示例:

package com.example.listener; import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeListener; import javax.servlet.http.HttpServletRequest; public class MyServletRequestAttributeListener implements ServletRequestAttributeListener { @Override public void attributeAdded(ServletRequestAttributeEvent srae) { HttpServletRequest request = (HttpServletRequest) srae.getServletRequest(); System.out.println("ServletRequest 属性添加:URL=" + request.getRequestURL() + ", name=" + srae.getName() + ", value=" + srae.getValue()); } @Override public void attributeRemoved(ServletRequestAttributeEvent srae) { HttpServletRequest request = (HttpServletRequest) srae.getServletRequest(); System.out.println("ServletRequest 属性移除:URL=" + request.getRequestURL() + ", name=" + srae.getName() + ", value=" + srae.getValue()); } @Override public void attributeReplaced(ServletRequestAttributeEvent srae) { HttpServletRequest request = (HttpServletRequest) srae.getServletRequest(); System.out.println("ServletRequest 属性替换:URL=" + request.getRequestURL() + ", name=" + srae.getName() + ", oldValue=" + srae.getValue() + ", newValue=" + srae.getServletRequest().getAttribute(srae.getName())); } }

配置 ServletRequestAttributeListener:

web.xml 文件中配置 ServletRequestAttributeListener

<listener> <listener-class>com.example.listener.MyServletRequestAttributeListener</listener-class> </listener>

应用场景:ServletRequest 监听器

  • 请求日志记录: 使用 ServletRequestListener 记录请求的开始和结束时间、请求 URL 等信息,用于请求日志和性能分析。

  • 请求参数预处理:requestInitialized() 中对请求参数进行预处理,例如字符编码转换、参数校验等。

  • 请求上下文传递: 通过 ServletRequest 的属性传递请求上下文信息,例如用户身份信息、跟踪 ID 等,方便在后续的 Servlet 或 Filter 中使用。

  • 安全审计: 监听 ServletRequest 的生命周期事件和属性变更事件,进行安全审计和异常检测。

5.3.3 监听器的配置与部署

Tomcat Listener 的配置和部署主要通过 web.xml 文件完成。在 web.xml 文件中,可以使用 <listener> 元素来声明一个监听器。

配置语法:

<web-app> ... <listener> <listener-class>listenerClassName</listener-class> </listener> ... </web-app>
  • <listener>: 声明一个监听器。

  • <listener-class>: 指定监听器类的完整类名 (包含包名)。

配置示例 (web.xml):

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <listener> <listener-class>com.example.listener.MyServletContextListener</listener-class> </listener> <listener> <listener-class>com.example.listener.MyHttpSessionListener</listener-class> </listener> <listener> <listener-class>com.example.listener.MyServletRequestListener</listener-class> </listener> </web-app>

部署:

将配置好的 web.xml 文件和编译后的监听器类 (.class 文件) 打包到 Web 应用的 WAR 包中,部署到 Tomcat 服务器即可。Tomcat 在启动 Web 应用时,会读取 web.xml 文件,并根据 <listener> 配置创建并注册相应的监听器。

监听器加载顺序:

web.xml 中配置的监听器,Tomcat 会按照它们在 web.xml 文件中声明的顺序进行加载和初始化。ServletContextListenercontextInitialized() 方法会在所有 Servlet 和 Filter 初始化之前被调用,而 contextDestroyed() 方法会在所有 Servlet 和 Filter 销毁之后被调用。

5.3.4 总结与最佳实践

Tomcat Listener 是一个非常强大的特性,它允许开发者在 Web 应用生命周期的关键时刻插入自定义逻辑,实现各种高级功能。合理地使用监听器可以提高应用的灵活性、可扩展性和可维护性。

最佳实践:

  • 明确监听器的职责: 每个监听器应该只负责监听一种或几种相关的事件,避免监听器过于臃肿,职责不清晰。

  • 避免在监听器中执行耗时操作: 监听器方法应该尽可能快速地执行完成,避免阻塞 Tomcat 的事件处理线程。如果需要执行耗时操作,应该考虑使用异步线程或消息队列等机制。

  • 注意线程安全: 监听器实例通常是单例的,在多线程环境下可能会被并发访问,因此需要注意线程安全问题,避免共享变量的竞争条件。

  • 合理选择监听器类型: 根据实际需求选择合适的监听器类型,例如生命周期管理使用 ServletContextListener、会话管理使用 HttpSessionListener、请求处理使用 ServletRequestListener

  • 充分利用事件对象: 事件对象 (例如 ServletContextEvent, HttpSessionEvent, ServletRequestEvent) 包含了丰富的上下文信息,应该充分利用这些信息来实现更精细化的事件处理逻辑。

总结:

Tomcat Listener 是构建健壮、可扩展 Web 应用的重要组成部分。通过深入理解和灵活运用各种类型的监听器,开发者可以更好地掌控 Web 应用的生命周期和事件流程,实现更加复杂和高级的功能,提升 Web 应用的整体质量和用户体验。希望本章节的详细介绍和代码实践能够帮助你更好地理解和应用 Tomcat Listener 技术。


发布者: 作者: 转发
评论区 (0)
U