监听器(Listener)详解

一、什么是监听器?

  • 定义

    监听器是 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());
        // 释放资源
    }
}

测试方法

  1. 部署应用到 Tomcat。
  2. 启动 Tomcat,观察控制台输出 应用已启动!
  3. 停止 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);
        }
    }
}

测试方法

  1. 访问 /testContextAttr
  2. 控制台输出:

⑶.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);
    }
}

测试方法

  1. 访问任意 Servlet(如根路径 /)。
  2. 控制台输出:

上面代码依旧放着运行

⑷.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 已销毁!");
    }
}

测试方法

  1. 访问 /testSession
  2. 控制台输出:

⑵.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) 面向方法的前后增强 日志记录、事务管理
相关推荐
Moe4885 分钟前
Spring AI Advisors:从链式增强到递归顾问
java·后端
敖正炀9 分钟前
ReentrantReadWriteLock、ReentrantLock、synchronized 对比
java
cike_y20 分钟前
Java反序列化漏洞-Shiro721流程分析
java·反序列化·shiro框架
极创信息41 分钟前
信创系统认证服务怎么做?从适配到验收全流程指南
java·大数据·运维·tomcat·健康医疗
格鸰爱童话1 小时前
向AI学习项目技能(六)
java·人工智能·spring boot·python·学习
白宇横流学长1 小时前
停车场管理系统的设计与实现
java
Flittly1 小时前
【SpringAIAlibaba新手村系列】(18)Agent 智能体与今日菜单应用
java·spring boot·agent
木井巳1 小时前
【递归算法】目标和
java·算法·leetcode·决策树·深度优先
亦暖筑序1 小时前
手写 Spring AI Agent:让大模型自主规划任务,ReAct 模式全流程拆解
java·人工智能·spring
敖正炀1 小时前
ReentrantLock 与 synchronized对比
java