一、什么是监听器?
定义
监听器是 Java Servlet 规范 的核心组件之一,与 Filter(过滤器)并列。它以接口形式定义,命名均以Listener
结尾,用于监听 Web 应用中的关键事件(如对象创建、销毁、属性变化等)。核心角色
监听器是 Servlet 规范为开发者提供的 “事件钩子”,允许在特定时刻(如应用启动、Session 创建、请求初始化)插入自定义逻辑。
二. 监听器的作用
捕获关键事件
监听器的作用是 在特定时机自动触发代码逻辑。例如:应用启动时初始化全局配置
Session 创建时记录在线用户
请求结束时统计耗时
解耦业务逻辑
将系统级操作(如资源初始化、安全审计)与业务代码分离,提升代码可维护性。
三、Servlet 规范中的监听器分类
监听器中的方法不需要程序员手动调用。是发生某个特殊事件之后被服务器调用。
Servlet 规范提供了 8 种监听器接口,分为两类包:
1. jakarta.servlet
包下的监听器
监听器接口 | 作用 | 典型场景 |
---|---|---|
ServletContextListener |
监听应用的启动和关闭 | 初始化数据库连接池、加载全局配置 |
ServletContextAttributeListener |
监听全局作用域(Context)属性变化 | 跟踪全局配置的动态更新 |
ServletRequestListener |
监听请求的初始化和销毁 | 记录请求耗时、统计请求量 |
ServletRequestAttributeListener |
监听请求作用域(Request)属性变化 | 监控请求参数的动态修改 |
⑴.ServletContextListener
作用:监听应用的启动和关闭
典型场景:初始化数据库连接池、加载全局配置
监听器代码
package oop1;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class AppInitListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("Web 应用启动!");
System.out.println("应用已启动!时间:" + System.currentTimeMillis());
// 初始化资源
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Web 应用关闭!");
System.out.println("应用已关闭!时间:" + System.currentTimeMillis());
// 释放资源
}
}
测试方法
- 部署应用到 Tomcat。
- 启动 Tomcat,观察控制台输出
应用已启动!
。 - 停止 Tomcat,观察控制台输出
应用已关闭!
。
⑵.ServletContextAttributeListener
作用:监听全局作用域(Context)属性变化
典型场景:跟踪全局配置的动态更新
监听器代码
import jakarta.servlet.ServletContextAttributeEvent;
import jakarta.servlet.ServletContextAttributeListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class GlobalAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent event) {
System.out.println("全局属性新增:" + event.getName() + " = " + event.getValue());
}
@Override
public void attributeRemoved(ServletContextAttributeEvent event) {
System.out.println("全局属性删除:" + event.getName());
}
@Override
public void attributeReplaced(ServletContextAttributeEvent event) {
System.out.println("全局属性替换:" + event.getName() + " 新值:" + event.getValue());
}
}
测试代码(测试 Servlet)
package oop1;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
@WebServlet("/testContextAttr")
public class TestContextAttrServlet extends HttpServlet {
private static final Logger LOGGER = Logger.getLogger(TestContextAttrServlet.class.getName());
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
try {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
getServletContext().setAttribute("globalConfig", "初始值");
getServletContext().setAttribute("globalConfig", "更新后的值");
getServletContext().removeAttribute("globalConfig");
response.getWriter().write("全局属性操作完成");
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "处理请求时发生 I/O 异常", e);
}
}
}
测试方法
- 访问
/testContextAttr
。 - 控制台输出:
⑶.ServletRequestListener
作用:监听请求的初始化和销毁
典型场景:记录请求耗时、统计请求量
监听器代码
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class RequestLifeCycleListener implements ServletRequestListener {
private static int requestCount = 0;
@Override
public void requestInitialized(ServletRequestEvent event) {
requestCount++;
System.out.println("请求初始化!当前请求总数:" + requestCount);
}
@Override
public void requestDestroyed(ServletRequestEvent event) {
System.out.println("请求销毁!当前请求总数:" + requestCount);
}
}
测试方法
- 访问任意 Servlet(如根路径
/
)。 - 控制台输出:
上面代码依旧放着运行
⑷.ServletRequestAttributeListener
作用:监听请求作用域(Request)属性变化
典型场景:监控请求参数的动态修改
监听器代码
import jakarta.servlet.ServletRequestAttributeEvent;
import jakarta.servlet.ServletRequestAttributeListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class RequestAttributeListener implements ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent event) {
System.out.println("请求属性新增:" + event.getName() + " = " + event.getValue());
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent event) {
System.out.println("请求属性删除:" + event.getName());
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent event) {
System.out.println("请求属性替换:" + event.getName() + " 新值:" + event.getValue());
}
}
测试代码(测试 Servlet)
package oop1;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/testRequestAttr")
public class TestRequestAttrServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 操作请求属性
request.setAttribute("user", "Alice");
request.setAttribute("user", "Bob"); // 触发替换事件
request.removeAttribute("user"); // 触发删除事件
response.getWriter().write("请求属性操作完成");
}
}
测试方法
访问 /testRequestAttr。
控制台输出:
2.jakarta.servlet.http
包下的监听器
监听器接口 | 作用 | 典型场景 |
---|---|---|
HttpSessionListener |
监听 Session 的创建和销毁 | 统计在线用户数量 |
HttpSessionAttributeListener |
监听 Session 作用域属性变化 | 记录用户登录/登出行为 |
HttpSessionBindingListener |
监听对象与 Session 的绑定/解绑 | 用户登录时绑定对象,登出时自动解绑 |
HttpSessionIdListener |
监听 Session ID 的变更 | 安全审计(检测 Session 固定攻击) |
HttpSessionActivationListener |
监听 Session 的钝化(持久化)和活化(恢复) | 集群环境下 Session 的分布式存储 |
⑴.HttpSessionListener
作用:监听 Session 的创建和销毁
典型场景:统计在线用户数量
监听器代码
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class SessionCounterListener implements HttpSessionListener {
private static int activeSessions = 0;
@Override
public void sessionCreated(HttpSessionEvent se) {
activeSessions++;
System.out.println("Session 创建!当前在线用户:" + activeSessions);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
activeSessions--;
System.out.println("Session 销毁!当前在线用户:" + activeSessions);
}
}
测试代码(测试 Servlet)
package oop1;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/testSession")
public class TestSessionServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 创建 Session(触发 sessionCreated)
request.getSession();
response.getWriter().write("Session 已创建!<br>");
// 销毁 Session(触发 sessionDestroyed)
request.getSession().invalidate();
response.getWriter().write("Session 已销毁!");
}
}
测试方法
- 访问
/testSession
。 - 控制台输出:
⑵.HttpSessionAttributeListener
作用:监听 Session 作用域属性变化
典型场景:记录用户登录/登出行为
监听器代码
import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class SessionAttributeLogger implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
System.out.println("Session 属性新增:"
+ event.getName() + " = " + event.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
System.out.println("Session 属性删除:" + event.getName());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
System.out.println("Session 属性替换:"
+ event.getName() + " 新值:" + event.getValue());
}
}
测试代码(测试 Servlet)
package oop1;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/testSessionAttr")
public class TestSessionAttrServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 获取 Session
var session = request.getSession();
// 添加属性(触发 attributeAdded)
session.setAttribute("user", "Alice");
// 替换属性(触发 attributeReplaced)
session.setAttribute("user", "Bob");
// 删除属性(触发 attributeRemoved)
session.removeAttribute("user");
response.getWriter().write("Session 属性操作完成!");
}
}
⑶.HttpSessionBindingListener
作用:监听对象与 Session 的绑定/解绑
典型场景:用户登录时绑定对象,登出时自动解绑
监听器对象代码(用户类)
public class User implements HttpSessionBindingListener {
private String username;
public User(String username) {
this.username = username;
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println(username + " 已绑定到 Session!绑定名:" + event.getName());
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println(username + " 已解绑!Session ID:" + event.getSession().getId());
}
}
测试代码(测试 Servlet)
package oop1;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/testSessionBinding")
public class TestSessionBindingServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 创建 Session
var session = request.getSession();
// 绑定 User 对象(触发 valueBound)
User user = new User("Charlie");
session.setAttribute("user", user);
// 解绑 User 对象(触发 valueUnbound)
session.removeAttribute("user");
response.getWriter().write("对象绑定/解绑完成!");
}
}
⑷.HttpSessionIdListener
作用:监听 Session ID 的变更
典型场景:安全审计(检测 Session 固定攻击)
监听器代码
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionIdListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class SessionIdChangeListener implements HttpSessionIdListener {
@Override
public void sessionIdChanged(HttpSessionEvent se, String oldSessionId) {
System.out.println("Session ID 变更!旧 ID:" + oldSessionId
+ " → 新 ID:" + se.getSession().getId());
}
}
测试代码(测试 Servlet)
package oop1;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/testSessionId")
public class TestSessionIdServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 创建 Session 并获取旧 ID
var session = request.getSession();
String oldId = session.getId();
// 触发 Session ID 变更
String newId = request.changeSessionId();
// 输出结果
response.getWriter().write("旧 Session ID:" + oldId + "<br>"
+ "新 Session ID:" + newId);
}
}
四、监听器的核心特性
1. 自动触发
所有监听器方法由 服务器自动调用,开发者无需手动干预。例如:
contextInitialized()
在应用启动时触发sessionDestroyed()
在 Session 超时或手动失效时触发
2. 作用域与线程安全
单例模式:监听器实例由容器创建且全局唯一。
线程安全:避免在监听器中使用实例变量,防止多线程竞争。
五、监听器 vs 过滤器 vs 拦截器
组件 | 作用层级 | 主要用途 | 典型场景 |
---|---|---|---|
Listener | 容器级别 | 监听应用/会话/请求生命周期 | 资源初始化、在线统计 |
Filter | Web 请求级别 | 拦截处理 HTTP 请求/响应 | 编码设置、权限校验 |
Interceptor | 框架级别(如Spring) | 面向方法的前后增强 | 日志记录、事务管理 |