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

finalize() 另類妙用:Web 應用中 Session 失效後的用戶在線狀態自動清理

     編輯:Java 相關知識

場景痛點:HTTP 無狀態下的「用戶在線」追蹤難題

Web 應用中,用戶關閉瀏覽器、跳轉外站、進程崩潰等非正常登出不會觸發 logout 動作。若僅依賴顯式登出,Application 級在線用戶列表將殘留僵屍數據,影響管理員決策(如備份窗口、系統升級通知)。

解決方案:finalize() 作為「最後防線」

利用 JVM 垃圾回收機制:Session 失效 → 關聯業務對象不可達 → GC 調用 finalize() → 執行清理邏輯。

核心類設計

1. 用戶數據載體

package com;
public class UserInfo {
    public String name = "";
    public String sessionId = "";
}

2. 會話管理器:TestSession

package com;
import java.util.Vector;

public class TestSession {
    public UserInfo user = new UserInfo();
    private Vector onlineUsers;  // 應用級共享列表引用

    // 驗證邏輯省略
    public boolean verify(String username, String password) { return true; }

    // 注入共享列表引用
    public void setSessionVar(String sessionId, Vector appVector) {
        this.user.sessionId = sessionId;
        this.onlineUsers = appVector;
    }

    // 線程安全添加用戶
    private static synchronized void addOnlineUser(UserInfo u, Vector vec) {
        if (u == null || vec == null) return;
        for (int i = 0; i < vec.size(); i++) {
            if (((UserInfo)vec.get(i)).sessionId.equals(u.sessionId)) {
                vec.set(i, u);  // 更新已有
                return;
            }
        }
        vec.add(u);  // 新增
    }

    // 線程安全移除用戶
    private static synchronized void removeOnlineUser(UserInfo u, Vector vec) {
        if (u == null || vec == null) return;
        for (int i = 0; i < vec.size(); i++) {
            if (((UserInfo)vec.get(i)).sessionId.equals(u.sessionId)) {
                vec.remove(i);
                return;
            }
        }
    }

    // 核心:finalize() 作為安全網
    protected void finalize() {
        removeOnlineUser(this.user, this.onlineUsers);
    }

    // 業務入口
    public boolean login(String username) {
        this.user.name = username;
        addOnlineUser(this.user, this.onlineUsers);
        return true;
    }

    public boolean logout() {
        this.finalize();  // 顯式登出時主動調用
        this.user = null;
        this.onlineUsers = null;
        return true;
    }
}

JSP 頁面整合:loginTest.jsp


<%
    String action = request.getParameter("actiontype");
    TestSession ts = (TestSession) session.getAttribute("testSession");
    Vector appUsers = (Vector) application.getAttribute("app_users");
    if (appUsers == null) {
        appUsers = new Vector();
        application.setAttribute("app_users", appUsers);
    }

    if ("login".equals(action)) {
        if (ts != null) {  // 已登入則先登出
            session.removeAttribute("testSession");
            if (!ts.user.name.equals("")) ts.logout();
        }
        ts = new TestSession();
        if (!ts.verify(request.getParameter("username"), request.getParameter("password"))) {
            out.print("登入失敗");
        } else {
            session.setAttribute("testSession", ts);
            ts.setSessionVar(session.getId(), appUsers);
            ts.login(request.getParameter("username"));
            out.print("登入成功");
        }
    } else if ("logout".equals(action)) {
        if (ts != null) {
            session.removeAttribute("testSession");
            if (!ts.user.name.equals("")) {
                out.print(ts.user.name + " 登出成功");
                ts.logout();
            }
        }
        session.invalidate();
    }
%>

監控頁面:viewSessions.jsp


<%
    Vector vec = (Vector) application.getAttribute("app_users");
    if (vec != null) {
        for (int i = 0; i < vec.size(); i++) {
            UserInfo u = (UserInfo) vec.get(i);
            out.println(u.name + " | Session: " + u.sessionId + "
"); } } %>

運行驗證流程

  1. IE1 登入 userA → 監控頁顯示 userA
  2. IE2 登入 userB → 監控頁顯示 userA, userB
  3. IE1 點「登出」 → 監控頁剩 userB
  4. 關鍵測試:IE2 直接關閉瀏覽器(不點登出) → 等待 Session 超時 (默認 30min) → GC 運行 → finalize() 執行 → 監控頁 userB 自動消失

為何用 static synchronized?

  • onlineUsers 是 Application 級共享對象,所有用戶線程並發訪問
  • 實例級 synchronized 僅鎖住單個 TestSession,無法防止多用戶同時操作共享列表
  • static synchronized 鎖住 TestSession.class(Class 對象),實現類級別互斥,任意時刻僅一線程能進入 add/remove

finalize() 使用鐵律(必讀)

  1. 不可預測性:GC 何時運行、finalize 何時調用完全不可控,絕不可用於釋放稀缺資源(文件句柄、數據庫連接、鎖)。
  2. 異常吞沒:finalize 拋出的異常會被 GC 忽略並終止最終化過程,導致對象「不死」洩漏。
  3. 性能代價:帶 finalize 的對象需兩輪 GC 才能回收,增加 GC 壓力。
  4. 唯一適配場景:非關鍵、冪等、補償性清理——如本例中「僵屍用戶最終會被清理」,即使延遲幾分鐘業務也能容忍。

現代替代方案

  • HttpSessionBindingListener:實現 valueUnbound(),Session 失效時確定性回調,無需依賴 GC。
  • SessionDestroyedEvent (Servlet 3.0+):@WebListener 監聽 HttpSessionEvent,集中處理清理邏輯。
  • 定時掃描任務:Quartz/Spring Scheduling 定期掃描 lastAccessedTime 超時用戶並清理。

總結

finalize() 是 JVM 提供的「最後機會」,而非「首選工具」。在 HttpSessionBindingListener 等標準機制缺失的舊版本 Servlet 環境中,它能作為補償性安全網解決非正常登出清理問題。但現代開發中,請優先選擇確定性生命週期回調,將 finalize() 留給真正無法以其他方式處理的邊緣場景(如 JNI 關聯的原生資源釋放)。

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