在 Java Web 開發中,監聽器 是觀察者模式在 Servlet 規範中的標準實現。它允許開發者監聽 Web 應用中核心域對象(ServletContext、HttpSession、ServletRequest)的生命週期事件(創建、銷毀)及屬性變更(添加、移除、替換),從而在關鍵節點注入自定義邏輯,如初始化緩存、統計在線用戶、記錄請求鏈路等。
@WebListener
public class MyAppListener implements ServletContextListener, HttpSessionListener {
// 實現對應接口方法
}
無需配置 web.xml,容器啟動時自動掃描註冊。
<listener>
<listener-class>com.example.MyAppListener</listener-class>
</listener>
| 域對象 | 生命週期監聽器 | 屬性變更監聽器 | 其他專用監聽器 |
|---|---|---|---|
| ServletContext | ServletContextListener | ServletContextAttributeListener | — |
| HttpSession | HttpSessionListener | HttpSessionAttributeListener | HttpSessionBindingListener, HttpSessionActivationListener, HttpSessionIdListener (3.1+) |
| ServletRequest | ServletRequestListener | ServletRequestAttributeListener | AsyncListener (異步請求) |
核心接口: 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());
}
}
核心接口:
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());
// 可在此執行登出邏輯、清理購物車、釋放資源
}
}
核心接口: ServletRequestListener (requestInitialized / requestDestroyed)、ServletRequestAttributeListener
典型場景:
@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 防止內存洩漏
}
}
requestInitialized 實際上是從池取出/重置對象,requestDestroyed 是歸還池中,非新建/銷毀對象。request.startAsync()) 會觸發屬性替換事件,需關注 AsyncListener 而非標準 Request 監聽器。@WebListener 和 web.xml 配置會導致重複執行,二選一。HttpSessionBindingListener 實現自感知綁定/解綁,業務邏輯內聚,無需外部監聽器維護映射關係。Servlet 監聽器是 Web 容器提供的強大擴展點,合理運用可實現應用級資源管理、會話監控、請求追蹤等核心功能。掌握其生命週期、事件觸發時機及線程模型,是構建穩健、可觀測 Java Web 應用的關鍵技能。