Servlet 監聽器是 Web 應用事件模型的核心組件,通過**觀察者模式**監聽 ServletContext、HttpSession、ServletRequest 三大域對象的**生命週期**與**屬性變化**事件。本文系統梳理八大標準監聽器接口、註冊配置、兩大經典實戰場景 (在線人數統計、數據源初始化) 及最佳實踐。
| 接口 | 監聽對象 | 觸發事件 | 核心方法 |
|---|---|---|---|
ServletContextListener | ServletContext (應用級) | 初始化 / 銷毀 | contextInitialized/contextDestroyed |
ServletContextAttributeListener | ServletContext 屬性 | 增 / 刪 / 改 | attributeAdded/attributeRemoved/attributeReplaced |
HttpSessionListener | HttpSession (會話級) | 創建 / 失效 | sessionCreated/sessionDestroyed |
HttpSessionActivationListener | 綁定到 Session 的對象 | 鈍化 / 活化 (分布式遷移) | sessionWillPassivate/sessionDidActivate |
HttpSessionAttributeListener | Session 屬性 | 增 / 刪 / 改 | attributeAdded/attributeRemoved/attributeReplaced |
HttpSessionBindingListener | 綁定到 Session 的對象 | 綁定 / 解綁 | valueBound/valueUnbound |
ServletRequestListener | ServletRequest (請求級) | 初始化 / 銷毀 | requestInitialized/requestDestroyed |
ServletRequestAttributeListener | Request 屬性 | 增 / 刪 / 改 | attributeAdded/attributeRemoved/attributeReplaced |
<listener>
<listener-class>com.demo.listener.UserListener</listener-class>
</listener>
<listener>
<listener-class>com.demo.listener.DataSourceListener</listener-class>
</listener>
@WebListener
public class UserListener implements HttpSessionBindingListener { ... }
優勢: 無需 XML、類掃描自動註冊、支援動態添加 (ServletContext.addListener())。
核心思想: 實現 HttpSessionBindingListener 的對象被放入 Session 時自動觸發 valueBound,移除/失效時觸發 valueUnbound,**無需在 Listener 類中手動獲取 Session**。
package com.demo.listener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
public class UserListener implements HttpSessionBindingListener {
// 被放入 Session 時調用
public void valueBound(HttpSessionBindingEvent event) {
OnlineCounter.increment(); // 線程安全計數器
System.out.println("用戶上線:" + event.getName() + " 當前在線:" + OnlineCounter.getCount());
}
// 從 Session 移除/失效時調用
public void valueUnbound(HttpSessionBindingEvent event) {
OnlineCounter.decrement();
System.out.println("用戶下線:" + event.getName() + " 當前在線:" + OnlineCounter.getCount());
}
}
使用方式: 登入成功後 session.setAttribute("user", new UserListener()),**對象本身即監聽器**,極其優雅。
package com.demo.listener;
import javax.servlet.*;
import javax.naming.*;
import javax.sql.DataSource;
public class DataSourceListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
ServletContext sc = event.getServletContext();
try {
Context ctx = new InitialContext();
// JNDI 查找容器配置的數據源
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/mydb");
// 存入應用域,全局可用
sc.setAttribute("dataSource", ds);
System.out.println("DataSource 初始化完成並綁定到 ServletContext");
} catch (NamingException e) {
throw new RuntimeException("DataSource 初始化失敗", e);
}
}
public void contextDestroyed(ServletContextEvent event) {
// 清理資源 (如關閉連接池)
ServletContext sc = event.getServletContext();
DataSource ds = (DataSource) sc.getAttribute("dataSource");
if (ds instanceof org.apache.commons.dbcp2.BasicDataSource) {
try { ((org.apache.commons.dbcp2.BasicDataSource) ds).close(); } catch (Exception ignored) {}
}
}
}
Servlet 使用:
DataSource ds = (DataSource) getServletContext().getAttribute("dataSource");
try (Connection conn = ds.getConnection()) {
// 業務邏輯
}
web.xml 或掃描順序聲明順序調用 contextInitialized;銷毀逆序。contextInitialized 由容器單線程啟動時調用,安全;sessionCreated/requestInitialized **多線程併發調用**,操作共享數據 (如計數器) **必須用原子類或同步**。contextInitialized 拋出異常將導致應用啟動失敗;其他監聽器異常僅記錄日誌不影響主流程。contextDestroyed 關閉,防洩漏。Servlet 監聽器是 Web 容器事件總線的**官方接入點**。掌握八大接口的**監聽對象、觸發時機、核心方法**,配合 @WebListener 註解,能優雅解決:應用啟動初始化、在線用戶統計、請求鏈路追蹤、屬性變更審計、分布式 Session 遷移 等橫切關注點。將監聽器作為「基礎設施代碼」而非「業務代碼」編寫,是架構整潔的關鍵。