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

Spring Singleton 陷阱:基類注入用戶上下文導致多用戶數據錯亂

     編輯:Java 程式設計入門

問題場景重現

項目使用 Spring + Acegi Security。為便捷獲取當前登錄用戶,在 BaseService/BaseDao 提供:


protected UserContext getUserContext() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    return (UserContext) auth.getPrincipal();
}
protected UserInfo getUserInfo() { return getUserContext().getUserInfo(); }

子類 SomeService 為「優化」在字段緩存用戶信息:


public class SomeService extends BaseService {
    private UserInfo user = super.getUserInfo(); // 陷阱在此!
    
    public void someMethod() {
        int uid = this.user.getUserId(); // 永遠是第一個登錄用戶
        ...
    }
}

根因分析

  • Spring Bean 默認 singleton 作用域,容器僅創建一個 SomeService 實例
  • 字段 user 在 Bean 初始化/依賴注入階段賦值(第一次調用 getUserInfo())
  • 後續所有請求復用同一實例,字段不再更新,導致所有用戶看到第一個用戶的數據
  • 單元測試通常單線程單用戶,無法暴露此問題

正確姿勢:方法級獲取,拒絕字段緩存


public class SomeService extends BaseService {
    public void someMethod() {
        // 每次調用實時從 ThreadLocal (SecurityContextHolder) 獲取
        int uid = super.getUserInfo().getUserId();
        String name = super.getUserInfo().getUserName();
        ...
    }
}

SecurityContextHolder 內部使用 ThreadLocal 隔離線程上下文,每次調用獲取的是當前請求線程綁定的用戶,天然線程安全。

擴展:Spring Bean 作用域與狀態管理原則

最佳實踐清單

  1. Service/DAO 必須無狀態:不聲明非 final 實例字段保存業務數據
  2. 請求上下文數據(用戶、權限、參數)通過方法參數或 ThreadLocal 獲取,不緩存到實例字段
  3. 若確需有狀態組件,聲明 @Scope("prototype") 或 request/session,並配合 @Lookup 或 ObjectFactory 注入
  4. 代碼審查重點檢查:Base 類便利方法是否被子類誤用為字段初始化
  5. 壓力測試/多用戶並發測試驗證無數據串聯

總結

Spring Singleton 是性能優化選擇,而非架構免死金牌。無狀態設計是分佈式、高併發系統的基石。將「便利」的基類方法誤用為字段初始化,本質是將請求級數據錯誤提升為實例級狀態。牢記:super.getXxx() 必須寫在方法體內,而非字段聲明處。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
作用域生命周期適用場景狀態風險
singleton (默認)容器級,唯一實例無狀態 Service、DAO、Controller、工具類高:嚴禁持有請求/會話級數據
prototype每次獲取新實例有狀態對象、命令模式、重量級配置對象低:但需自行管理銷毀
request單次 HTTP 請求Web 層表單對象、請求級緩存無
session用戶會話購物車、用戶偏好無