首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java Web 核心:Servlet 監聽器全面解析與實戰應用

     編輯:Java 綜合教學

Java Web 核心:Servlet 監聽器全面解析與實戰應用

在 Java Web 開發中,監聽器 是觀察者模式在 Servlet 規範中的標準實現。它允許開發者監聽 Web 應用中核心域對象(ServletContext、HttpSession、ServletRequest)的生命週期事件(創建、銷毀)及屬性變更(添加、移除、替換),從而在關鍵節點注入自定義邏輯,如初始化緩存、統計在線用戶、記錄請求鏈路等。

一、 註冊方式:註解 vs XML

1. @WebListener 註解 (Servlet 3.0+ 推薦)

@WebListener
public class MyAppListener implements ServletContextListener, HttpSessionListener {
    // 實現對應接口方法
}

無需配置 web.xml,容器啟動時自動掃描註冊。

2. web.xml 配置 (傳統/兼容模式)

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

二、 三大核心域對象與對應監聽器體系

域對象生命週期監聽器屬性變更監聽器其他專用監聽器
ServletContextServletContextListenerServletContextAttributeListener—
HttpSessionHttpSessionListenerHttpSessionAttributeListenerHttpSessionBindingListener, HttpSessionActivationListener, HttpSessionIdListener (3.1+)
ServletRequestServletRequestListenerServletRequestAttributeListenerAsyncListener (異步請求)

三、 實戰詳解:各類監聽器的典型應用場景

1. ServletContext 監聽器:應用級初始化與銷毀

核心接口: ServletContextListener (contextInitialized / contextDestroyed)、ServletContextAttributeListener

典型場景:

  • 應用啟動時:初始化連接池、加載配置文件、啟動定時任務、預熱緩存。
  • 應用關閉時:關閉連接池、銷毀線程池、持久化內存數據、註銷註冊中心。
  • 屬性監聽:監控全局配置變更、共享對象替換。
@WebListener
public class AppContextListener implements ServletContextListener, ServletContextAttributeListener {
    private static final Logger log = LoggerFactory.getLogger(AppContextListener.class);

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext ctx = sce.getServletContext();
        log.info("=== 應用啟動,初始化核心資源 ===");
        // 初始化數據源、Redis、定時任務等
        // ctx.setAttribute("appStartTime", System.currentTimeMillis());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("=== 應用關閉,釋放所有資源 ===");
        // 關閉資源、持久化數據
    }

    @Override
    public void attributeAdded(ServletContextAttributeEvent event) {
        log.debug("Context 屬性新增: {} = {}", event.getName(), event.getValue());
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent event) {
        log.debug("Context 屬性替換: {} 舊值: {} 新值: {}", event.getName(), event.getValue(), event.getServletContext().getAttribute(event.getName()));
        // 注意:event.getValue() 返回的是【舊值】
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent event) {
        log.debug("Context 屬性移除: {} = {}", event.getName(), event.getValue());
    }
}

2. HttpSession 監聽器:會話管理與在線統計

核心接口:

  • HttpSessionListener:sessionCreated / sessionDestroyed — 統計在線人數、會話創建/銷毀日誌。
  • HttpSessionAttributeListener:屬性增刪改 — 監控用戶登錄狀態、購物車變更等。
  • HttpSessionBindingListener:值對象自身感知綁定/解綁 — 實現該接口的對象放入/移出 Session 時自動回調 valueBound/valueUnbound,無需在 web.xml 註冊,適合「用戶實體」等核心領域對象。
  • HttpSessionActivationListener:分布式環境下 Session 鈍化/活化 (序列化/反序列化) 回調 — 釋放/恢復資源 (如數據庫連接、文件句柄)。
  • HttpSessionIdListener (Servlet 3.1+):Session ID 變更監聽 (如安全重定向後換 ID)。

關鍵點: attributeReplaced 事件中 event.getValue() 返回的是【被替換的舊值】,新值需通過 session.getAttribute(name) 獲取。

@WebListener
public class SessionTracker implements HttpSessionListener, HttpSessionAttributeListener {
    private static final AtomicInteger ONLINE_COUNT = new AtomicInteger(0);

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        int count = ONLINE_COUNT.incrementAndGet();
        se.getSession().setAttribute("loginTime", System.currentTimeMillis());
        log.info("新會話創建: {}, 當前在線: {}", se.getSession().getId(), count);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        int count = ONLINE_COUNT.decrementAndGet();
        log.info("會話銷毀: {}, 當前在線: {}", se.getSession().getId(), count);
    }

    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        log.debug("Session [{}] 新增屬性: {} = {}", event.getSession().getId(), event.getName(), event.getValue());
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent event) {
        log.debug("Session [{}] 替換屬性: {} 舊值: {}", event.getSession().getId(), event.getName(), event.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent event) {
        log.debug("Session [{}] 移除屬性: {} = {}", event.getSession().getId(), event.getName(), event.getValue());
    }
}

// 實現 BindingListener 的領域對象示例
public class UserSession implements HttpSessionBindingListener, Serializable {
    private Long userId;
    private String username;
    // 獲取器/設置器...

    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        log.info("用戶 {} 綁定到 Session: {}", username, event.getSession().getId());
        // 可在此記錄登錄日誌、更新最後活躍時間等
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        log.info("用戶 {} 從 Session {} 解綁 (登出/超時)", username, event.getSession().getId());
        // 可在此執行登出邏輯、清理購物車、釋放資源
    }
}

3. ServletRequest 監聽器:請求鏈路追蹤與性能監控

核心接口: ServletRequestListener (requestInitialized / requestDestroyed)、ServletRequestAttributeListener

典型場景:

  • 請求進入:生成 TraceId、記錄開始時間、MDC 注入上下文 (便於日誌鏈路追蹤)。
  • 請求結束:計算耗時、記錄慢查詢、清理 ThreadLocal/MDC。
  • 屬性監聽:調試請求屬性流轉 (如拦截器間傳遞數據)。
@WebListener
public class RequestTracer implements ServletRequestListener {
    private static final Logger log = LoggerFactory.getLogger(RequestTracer.class);
    private static final String START_TIME_ATTR = "REQUEST_START_TIME";
    private static final String TRACE_ID_ATTR = "TRACE_ID";

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest req = sre.getServletRequest();
        String traceId = UUID.randomUUID().toString().replace("-", "");
        req.setAttribute(TRACE_ID_ATTR, traceId);
        req.setAttribute(START_TIME_ATTR, System.nanoTime());
        // MDC.put("traceId", traceId); // 配合 Logback/Log4j2 MDC 實現鏈路追蹤
        log.debug("請求進入: {} {}", req.getRemoteAddr(), ((HttpServletRequest)req).getRequestURI());
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        ServletRequest req = sre.getServletRequest();
        Long start = (Long) req.getAttribute(START_TIME_ATTR);
        if (start != null) {
            long costMs = (System.nanoTime() - start) / 1_000_000;
            if (costMs > 500) { // 慢請求告警閾值
                log.warn("慢請求檢測: {} 耗時 {}ms", ((HttpServletRequest)req).getRequestURI(), costMs);
            }
        }
        // MDC.remove("traceId"); // 清理 MDC 防止內存洩漏
    }
}

四、 監聽器執行順序與容器行為細節

  1. 啟動順序: Context 初始化 → Context 屬性添加 → Servlet 初始化 (load-on-startup) → Request/Session 監聽器按需觸發。
  2. 銷毀順序: Servlet 銷毀 → Session 銷毀 (觸發 attributeRemoved 清理所有屬性) → Context 銷毀。
  3. Request 對象復用: 容器通常使用對象池復用 Request 對象,requestInitialized 實際上是從池取出/重置對象,requestDestroyed 是歸還池中,非新建/銷毀對象。
  4. 異步請求: 啟動異步 (request.startAsync()) 會觸發屬性替換事件,需關注 AsyncListener 而非標準 Request 監聽器。

五、 最佳實踐與避坑指南

  1. 輕量級處理: 監聽器方法在容器關鍵線程中同步執行,嚴禁執行耗時操作 (DB 查詢、網絡調用、複雜計算),否則阻塞容器啟動/請求處理。耗時邏輯請拋入異步線程池。
  2. 線程安全: Context/Session 屬性監聽器可能被多線程並發調用,操作共享數據需加鎖或使用並發集合。
  3. 異常隔離: 監聽器內部異常不應拋出容器,建議捕獲並記錄錯誤日誌,防止影響主流程。
  4. 避免重複註冊: 同一監聽器類同時使用 @WebListener 和 web.xml 配置會導致重複執行,二選一。
  5. Session 序列化: 分布式/集群環境下,放入 Session 的對象必須實現 Serializable,且盡量輕量;大對象請存入 Redis,Session 僅存 Key。
  6. BindingListener 適配: 核心領域對象 (User、Cart) 實現 HttpSessionBindingListener 實現自感知綁定/解綁,業務邏輯內聚,無需外部監聽器維護映射關係。

Servlet 監聽器是 Web 容器提供的強大擴展點,合理運用可實現應用級資源管理、會話監控、請求追蹤等核心功能。掌握其生命週期、事件觸發時機及線程模型,是構建穩健、可觀測 Java Web 應用的關鍵技能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved