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

自定義 Session 掃描器:基於 HttpSessionListener 實現會話生命周期精確控制

     編輯:Java 綜合教學

自定義 Session 掃描器:基於 HttpSessionListener 實現會話生命周期精確控制

Servlet 容器默認 Session 超時機制(web.xml session-timeout)存在不準確、無法主動清理、集群不同步等問題。通過實現 HttpSessionListener + ServletContextListener,構建自定義掃描器,實現 Session 創建跟蹤、定時掃描失效、手動刷新等精確控制。

設計思路

  1. 監聽創建/銷毀:實現 HttpSessionListener,在 sessionCreated 將 Session 放入線程安全容器,sessionDestroyed 移除。
  2. 啟動定時掃描:實現 ServletContextListener,contextInitialized 啟動 Timer/ScheduledExecutorService,定期遍歷容器檢查 getLastAccessedTime()。
  3. 並發安全:使用 Collections.synchronizedList + ReentrantLock 或 CopyOnWriteArrayList 保護容器;迭代刪除用 Iterator.remove() 避免 ConcurrentModificationException。
  4. 失效判定:System.currentTimeMillis() - session.getLastAccessedTime() > maxInactiveInterval * 1000 則調用 session.invalidate()。

完整代碼實現

package com.example.listener;

import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.util.concurrent.*;

public class SessionScanner implements HttpSessionListener, ServletContextListener {

    // 線程安全容器:CopyOnWriteArrayList 讀多寫少場景性能優於 synchronizedList
    private final List sessions = new CopyOnWriteArrayList<>();

    // 定時任務執行器
    private ScheduledExecutorService scheduler;

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        sessions.add(session);
        System.out.println("Session 創建: " + session.getId() + ", 當前總數: " + sessions.size());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        sessions.remove(session);
        System.out.println("Session 銷毀: " + session.getId() + ", 當前總數: " + sessions.size());
    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // 每 30 秒掃描一次
        scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r, "Session-Scanner");
            t.setDaemon(true);
            return t;
        });
        scheduler.scheduleAtFixedRate(this::scanExpiredSessions, 30, 30, TimeUnit.SECONDS);
        System.out.println("SessionScanner 啟動,掃描間隔 30s");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        if (scheduler != null) scheduler.shutdown();
        sessions.clear();
        System.out.println("SessionScanner 關閉");
    }

    private void scanExpiredSessions() {
        long now = System.currentTimeMillis();
        int expiredCount = 0;
        for (HttpSession session : sessions) {
            try {
                long lastAccess = session.getLastAccessedTime();
                int maxInactive = session.getMaxInactiveInterval(); // 秒
                if (maxInactive > 0 && (now - lastAccess) > maxInactive * 1000L) {
                    session.invalidate(); // 觸發 sessionDestroyed 移除
                    expiredCount++;
                }
            } catch (IllegalStateException e) {
                // Session 已失效,從容器移除
                sessions.remove(session);
            }
        }
        if (expiredCount > 0) {
            System.out.println("掃描失效 Session: " + expiredCount + " 個, 剩餘: " + sessions.size());
        }
    }

    // 可選:暴露管理接口
    public List getActiveSessions() {
        return new ArrayList<>(sessions);
    }

    public void forceInvalidate(String sessionId) {
        for (HttpSession session : sessions) {
            if (session.getId().equals(sessionId)) {
                session.invalidate();
                break;
            }
        }
    }
}

web.xml 註冊

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

優化與擴展建議

場景方案
分布式集群配合 Spring Session + Redis,集中存儲 Session,掃描器部署在單獨節點或利用 Redis Keys 過期通知
海量 Session分桶掃描、異步處理、利用 Redis TTL 自動過期(無需掃描)
精確控制暴露 JMX/Actuator Endpoint,支持手動失效、查看詳情、導出報表
安全合規記錄 Session 創建/銷毀/失效審計日志(用戶、IP、時間、原因)

常見誤區

  • 直接遍歷 sessions 調用 remove():導致 ConcurrentModificationException。必須用 Iterator.remove() 或 CopyOnWriteArrayList。
  • 依賴容器 sessionDestroyed 回調:容器可能延遲調用、崩潰時不調用。掃描器主動 invalidate() 更可靠。
  • 忽略 getMaxInactiveInterval() 返回 -1:表示永不過期,需跳過。
  • Timer 線程非守護線程:導致應用無法正常關閉。設置 setDaemon(true) 或用 ScheduledExecutorService。

自定義 Session 掃描器將會話管理從「被動等待」轉為「主動掌控」,是構建高可用、可審計、可運維 Web 系統的關鍵基礎設施。

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