核心概念:觀察者模式在 Servlet 中的實踐
Listener 本質是 觀察者模式:Servlet 容器 (主題/被觀察者) 監聽生命週期事件,通過回調通知已註冊的 Listener (觀察者)。
八大標準監聽器介面 (Servlet 3.0+)
| 分類 | 介面 | 監聽事件 | 典型應用 |
| ServletContext | ServletContextListener | 應用啟動/銷毀 | 初始化緩存、加載配置、啟動定時任務、關閉連接池 |
| ServletContextAttributeListener | 屬性增/刪/改 | 監控全局屬性變更、審計日誌 |
| HttpSession | HttpSessionListener | Session 創建/銷毀 | 在線用戶統計、Session 持久化 |
| HttpSessionActivationListener | 鈍化/活化 | 分布式 Session 遷移、序列化控制 |
| HttpSessionAttributeListener | 屬性增/刪/改 | 購物車變更、權限動態刷新 |
| HttpSessionBindingListener | 綁定/解綁 (值本身實現) | 對象感知自身 Session 狀態、自動清理資源 |
| ServletRequest | ServletRequestListener | 請求初始化/銷毀 | 請求耗時統計、ThreadLocal 清理、Open Session In View |
| ServletRequestAttributeListener | 屬性增/刪/改 | 請求級數據追蹤、調試 |
核心介面源碼解析
事件基類體系
EventObject (java.util)
└── ServletContextEvent
├── ServletContextAttributeEvent (屬性名/值)
└── HttpSessionEvent
├── HttpSessionBindingEvent (屬性名/值)
└── ServletRequestEvent
└── ServletRequestAttributeEvent (屬性名/值)
關鍵介面定義
// 應用級
public interface ServletContextListener extends EventListener {
void contextInitialized(ServletContextEvent sce); // 啟動完成
void contextDestroyed(ServletContextEvent sce); // 關閉前 (所有 Servlet/Filter destroy 後)
}
public interface ServletContextAttributeListener extends EventListener {
void attributeAdded(ServletContextAttributeEvent scae);
void attributeRemoved(ServletContextAttributeEvent scae);
void attributeReplaced(ServletContextAttributeEvent scae); // 獲取舊值
}
// Session 級
public interface HttpSessionListener extends EventListener {
void sessionCreated(HttpSessionEvent se);
void sessionDestroyed(HttpSessionEvent se);
}
public interface HttpSessionBindingListener extends EventListener {
void valueBound(HttpSessionBindingEvent event); // session.setAttribute(this)
void valueUnbound(HttpSessionBindingEvent event); // remove/invalidate
}
// 請求級
public interface ServletRequestListener extends EventListener {
void requestInitialized(ServletRequestEvent sre); // 進入第一個 Filter/Servlet 前
void requestDestroyed(ServletRequestEvent sre); // 離開最後一個 Filter/Servlet 後
}
實戰示例
1. 應用啟動初始化 (ServletContextListener)
@WebListener
public class AppInitializer implements ServletContextListener {
private ScheduledExecutorService scheduler;
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext();
// 加載全局配置
String configPath = ctx.getInitParameter("config.path");
GlobalConfig.load(configPath);
// 初始化緩存
CacheManager.init(ctx);
// 啟動定時任務
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(
() -> CacheManager.cleanupExpired(),
1, 1, TimeUnit.HOURS
);
ctx.log("應用初始化完成");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
if (scheduler != null) scheduler.shutdown();
CacheManager.shutdown();
DataSourceUtils.closeAll();
sce.getServletContext().log("應用優雅關閉");
}
}
2. 在線用戶統計 (HttpSessionListener)
@WebListener
public class OnlineCounter implements HttpSessionListener {
private static final AtomicInteger ONLINE_COUNT = new AtomicInteger(0);
@Override
public void sessionCreated(HttpSessionEvent se) {
ONLINE_COUNT.incrementAndGet();
se.getSession().getServletContext().log("在線用戶: " + ONLINE_COUNT.get());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
ONLINE_COUNT.decrementAndGet();
}
public static int getOnlineCount() { return ONLINE_COUNT.get(); }
}
3. 購物車自動持久化 (HttpSessionBindingListener)
public class ShoppingCart implements HttpSessionBindingListener, Serializable {
private Map items = new ConcurrentHashMap<>();
private Long userId;
@Override
public void valueBound(HttpSessionBindingEvent event) {
// 放入 Session 時:從資料庫恢復
HttpSession session = event.getSession();
this.userId = (Long) session.getAttribute("userId");
if (userId != null) {
CartDAO.load(userId).ifPresent(cart -> this.items.putAll(cart.getItems()));
}
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
// 移出 Session 時:持久化到資料庫
if (userId != null && !items.isEmpty()) {
CartDAO.save(userId, this);
}
}
// 業務方法...
}
4. 請求耗時監控 (ServletRequestListener)
@WebListener
public class RequestTimer implements ServletRequestListener {
private static final ThreadLocal START_TIME = new ThreadLocal<>();
@Override
public void requestInitialized(ServletRequestEvent sre) {
START_TIME.set(System.nanoTime());
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
long start = START_TIME.get();
if (start != null) {
long durationMs = (System.nanoTime() - start) / 1_000_000;
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
// 記錄慢請求
if (durationMs > 1000) {
log.warn("慢請求: {} {} - {}ms", req.getMethod(), req.getRequestURI(), durationMs);
}
// 可接入 Metrics 系統 (Micrometer/Prometheus)
Metrics.timer("http.request.duration", "uri", req.getRequestURI())
.record(durationMs, TimeUnit.MILLISECONDS);
}
START_TIME.remove(); // 防洩漏
}
}
註冊方式對比
| 方式 | 優點 | 缺點 | 推薦場景 |
| web.xml <listener> | 標準、無框架依賴 | 配置分散、不支持條件註冊 | 傳統項目、共享庫 |
| @WebListener 註解 | 零配置、類旁可見 | 掃描啟動稍慢、無法動態禁用 | Spring Boot、現代項目 |
| 動態註冊 ServletContext.addListener() | 運行時決定、條件化 | 代碼較繁瑣 | 插件化、多租戶、功能開關 |
Listener vs Filter vs Servlet
| 組件 | 生命週期 | 核心職責 | 典型用例 |
| Listener | 應用/Session/請求級事件 | 監聽、回調、副作用 | 初始化、統計、清理、審計 |
| Filter | 請求處理鏈中 | 預處理/後處理、攔截 | 編碼、認證、日誌、壓縮、XSS |
| Servlet | 請求處理終點 | 業務邏輯、響應生成 | Controller、REST API、JSP |
最佳實踐總結
- 單一職責:一個 Listener 只做一類事
- 異步處理:耗時操作 (DB、網絡) 用線程池/異步 Servlet
- 異常隔離:Listener 異常不應影響主流程,捕獲並記錄
- 資源清理:contextDestroyed / requestDestroyed 必須清理 ThreadLocal、連接池、線程池
- 順序控制:多個 Listener 無順序保證,有依賴請合併或用初始化參數協調
總結
Servlet Listener 是 Web 應用的「神經系統」,負責感知生命週期事件並觸發業務邊界動作。掌握八大介面、事件傳遞機制、註冊方式與實戰模式,能讓你優雅地處理初始化、統計、緩存、持久化等橫切關注點。
- 上一篇文章: Java volatile 關鍵字深度解析:可見性、有序性、DCL 與底層原理
- 下一篇文章: Java 裝飾器模式深度解析:動態擴展對象職責,替代繼承爆炸
Copyright ©
程式碼之家 All Rights Reserved