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

JSP 基於 Session 的在線用戶統計實戰:HttpSessionBindingListener 實現精準監控

     編輯:J2EE

在線用戶統計的三大挑戰

  1. Servlet API 無 getActiveSessions() 方法,無法直接枚舉所有 Session。
  2. 用戶未必點擊「登出」,可能關閉瀏覽器、網路中斷、Session 逾時。
  3. 叢集環境下 Session 複製/持久化,單機統計失效。

核心方案:HttpSessionBindingListener 生命週期感知

實作此介面的物件被放入/移出 Session 時,容器自動回調:

public interface HttpSessionBindingListener extends EventListener {
    void valueBound(HttpSessionBindingEvent event);   // session.setAttribute() 時
    void valueUnbound(HttpSessionBindingEvent event); // session.removeAttribute()/逾時/失效時
}

關鍵優勢: 即使用戶未點登出,Session 逾時/失效時容器保證觸發 valueUnbound,實現精準清理。

實戰實作:OnlineUserTracker


public class OnlineUserTracker implements HttpSessionBindingListener {
    private ServletContext application;
    private static final String ACTIVE_SESSIONS_KEY = "activeSessions";
    
    public OnlineUserTracker(ServletContext application) {
        if (application == null) throw new IllegalArgumentException("Application context required");
        this.application = application;
    }
    
    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        HttpSession session = event.getSession();
        // 1. 獲取或建立全域 Session 列表
        @SuppressWarnings("unchecked")
        List activeSessions = (List) application.getAttribute(ACTIVE_SESSIONS_KEY);
        if (activeSessions == null) {
            activeSessions = new CopyOnWriteArrayList<>(); // 線程安全
            application.setAttribute(ACTIVE_SESSIONS_KEY, activeSessions);
        }
        
        // 2. 驗證用戶身份 (假設登入後存放 User 物件)
        User user = (User) session.getAttribute("user");
        if (user != null) {
            activeSessions.add(session); // 記錄 Session 而非 User,支援踢人、強制下線
        }
    }
    
    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        HttpSession session = event.getSession();
        @SuppressWarnings("unchecked")
        List activeSessions = (List) application.getAttribute(ACTIVE_SESSIONS_KEY);
        if (activeSessions != null) {
            activeSessions.remove(session); // 精準移除
        }
    }
}

登入/登出流程整合

登入時雙重綁定


public void login(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    
    User user = userService.authenticate(username, password);
    if (user == null) {
        req.setAttribute("error", "帳號或密碼錯誤");
        req.getRequestDispatcher("/login.jsp").forward(req, resp);
        return;
    }
    
    HttpSession session = req.getSession(true);
    session.setAttribute("user", user);                    // 業務用戶物件
    session.setAttribute("tracker", new OnlineUserTracker(getServletContext())); // 監控器
    
    resp.sendRedirect("/dashboard.jsp");
}

登出時主動清理


public void logout(HttpServletRequest req, HttpServletResponse resp) {
    HttpSession session = req.getSession(false);
    if (session != null) {
        // 1. 從全域列表移除 (valueUnbound 會被觸發,但主動移除更即時)
        ServletContext app = getServletContext();
        @SuppressWarnings("unchecked")
        List activeSessions = (List) app.getAttribute("activeSessions");
        if (activeSessions != null) {
            activeSessions.remove(session);
        }
        
        // 2. 清理 Session 所有屬性
        Enumeration names = session.getAttributeNames();
        while (names.hasMoreElements()) {
            session.removeAttribute(names.nextElement());
        }
        
        // 3. 記錄登出時間、審計日誌
        userService.recordLogout((User) session.getAttribute("user"));
        session.invalidate();
    }
    resp.sendRedirect("/login.jsp");
}

前端顯示在線用戶列表


<%@ page import="java.util.*, javax.servlet.http.*" %>
<%
    List activeSessions = (List) application.getAttribute("activeSessions");
%>
<h2>當前在線用戶 (共 <%= activeSessions != null ? activeSessions.size() : 0 %> 人)</h2>
<table border="1">
    <tr><th>用戶名</th><th>登入時間</th><th>IP</th><th>操作</th></tr>
    <%
        if (activeSessions != null) {
            for (HttpSession s : activeSessions) {
                User u = (User) s.getAttribute("user");
                if (u != null) {
    %>
    <tr>
        <td><%= u.getUsername() %></td>
        <td><%= new Date(s.getCreationTime()) %></td>
        <td><%= s.getAttribute("clientIP") %></td>
        <td><a href="admin/kick.jsp?sessionId=<%= s.getId() %>">強制下線</a></td>
    </tr>
    <%
            }
        }
    %>
</table>

進階議題與解決方案

議題解決方案
叢集環境 Session 共享使用 Redis/Memcached 集中存儲 Session,activeSessions 亦存入 Redis Set,所有節點讀寫同一數據源
大量用戶下 activeSessions 列表效能分頁顯示、緩存統計數字、異步更新前端
防止 Session Fixation 攻擊登入成功後 session.invalidate() 再 req.getSession(true) 建立新 Session
「發呆」用戶判定 (iframe 保活場景)引入「最後活躍時間」屬性,定時任務掃描超過閾值 (如 30 分鐘) 的 Session 主動 invalidate
監控器物件序列化問題 (叢集)OnlineUserTracker 不實作 Serializable,或標記 transient,避免跨節點序列化失敗

總結

HttpSessionBindingListener 是 Servlet 規範提供的「隱形鉤子」,無需輪詢、無需修改容器代碼,即可實現精準、即時、自動清理的在線用戶統計。核心模式:登入雙綁定 (User + Tracker)、逾時自動回調清理、Application 域共享列表、管理端讀取列表渲染。此模式同樣適用於購物車商品數統計、文檔協作在線者顯示、即時聊天室成員管理等場景。

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