监听器(Listener)详解

发布于:2025-04-02 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、什么是监听器?

  • 定义
    监听器是 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) 面向方法的前后增强 日志记录、事务管理